Просто хотел Вас предупредить: С++14 не будет обратно совместим с C++11 в одном аспекте constexpr
функций.
В С++11, если Вы определите constexpr
функцию-член, то она неявно получит спецификатор const
:
// C++11
struct NonNegative
{
int i;
constexpr int const& get() /*const*/ { return i; }
int& get() { return i; }
};
Первое объявление функции get
получит спецификатор const
, даже если мы не укажем это явно. Следовательно, эти две функции являются перегруженными: const
и не-const
версии.
В С++14 это будет уже не так: оба объявления будут определять одиннаковую, не-const
версию функции-члена с различающимися возвращаемыми значениями — это приведет к ошибке компиляции. Если Вы уже начали использовать constexpr
функции и надеетесь на неявный спецификатор const
, то я советую Вам начать добавлять его явно, чтобы Ваш код продолжал компилироваться, если Вы решите перейти на компиляторы С++14.
Что не так с неявным const
?
Проблемы начнутся, если Вы попытаетесь использовать наш тип следующим образом:
// C++11
constexpr int i = NonNegative{2}.get(); // ERROR
Согласно (несколько необычным) правилам С++, при выборе функции-члена для временного объекта, не-const
версия предпочтительней const
версии. Наша не-const
функция-член get
не является constexpr
, поэтому она не может быть использована для инициализации constexpr
переменной и мы получим ошибку на этапе компиляции. Мы не можем сделать эту функцию constexpr
, потому что это автоматически добавит спецификатор const
…
Я сказал, что правила подбора лучшей функции — необычны, потому что они немного противоречат тому, как мы выбираем лучшую функцию-нечлен для временных объектов. В этом случае мы предпочитаем const
lvalue ссылки к не-const
версии:
// C++11
constexpr int const& get(NonNegative const& n) { return n.i; }
constexpr int& get(NonNegative& n) { return n.i; }
NonNegative N = readValue();
constexpr int * P = &get(N);
int main()
{
*P = 1;
}
Смотрите, что получается: глобальная переменная N
не является константой. Поэтому вторая, не-const
перегруженная функция выбирается для вызова при инициализации указателя P
. Но не-const
функция при этом все равно имеет спецификатор constexpr
! А все потому, что правило "constexpr
означает const
" применяется только для неявного this
аргумента нестатической функции-члена. constexpr
функция может получить ссылку на не-const
объект и вернуть ссылку на не-const
подобъект. Здесь нету никаких проблем: адрес глобального объекта постоянен и известен во время компиляции. Однако значение по адресу P
не постоянно и может быть изменено позже.
Если предыдущий пример выглядит несколько надуманным, рассмотрим следующий, более реалистичный пример:
// C++11
constexpr NonNegative* address(NonNegative& n) { return &n; }
NonNegative n{0}; // non-const
constexpr NonNegative* p = address(n);
Здесь все работает отлично, но если Вы попытаетесь сделать address
функцией членом, она перестанет работать:
// C++11
struct NonNegative
{
// ...
constexpr NonNegative* maddress() { return this; } // ERROR
};
NonNegative n{0}; // non-const
constexpr NonNegative* p = n.maddress();
Это потому, что maddress
неявно определен со спецификатором const
, this
имеет тип NonNegative const*
и не может быть конвертировано к NonNegative*
.
Следует отметить, что это не сама функция-член является const
, а неявный (this
) аргумент функции. Объявление функции-члена может быть переписано в псевдо-коде как:
// PSEUDO CODE
struct NonNegative
{
// ...
constexpr NonNegative* maddress(NonNegative const& (*this));
};
И этот неявный аргумент функции, в отличие от других аргументов функций, получает (иногда нежелательный) спецификатор const
.
Эта асимметричность будет удалена в С++14. Если Вы хотите спецификатор const
для неявного аргумента (this
), Вам следует добавить его самим. Следующий код будет действительным в C++14:
// C++14
struct NonNegative
{
int i;
constexpr int const& get() const { return i; }
constexpr int& get() { return i; }
};
Автор: Thekondr