«constexpr» функции не имеют спецификатор «const»

в 4:39, , рубрики: backward compatibility, c++, c++11, C++14, constexpr, переводы

Просто хотел Вас предупредить: С++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

Источник

Поделиться

* - обязательные к заполнению поля