Constexpr инициализирует статический член, используя статическую функцию
Требования
Я хочу значение constexpr
(т.е. константу времени компиляции), вычисленную из функции constexpr
. И я хочу, чтобы оба эти области были охвачены пространством имен класса, то есть статическим методом и статическим членом класса.
Первая попытка
Я сначала написал это (мне) очевидным образом:
class C1 {
constexpr static int foo(int x) { return x + 1; }
constexpr static int bar = foo(sizeof(int));
};
g++-4.5.3 -std=gnu++0x
говорит следующее:
error: ‘static int C1::foo(int)’ cannot appear in a constant-expression
error: a function call cannot appear in a constant-expression
g++-4.6.3 -std=gnu++0x
жалуется:
error: field initializer is not constant
Вторая попытка
Хорошо, подумал я, возможно, мне нужно переместить вещи из класса. Поэтому я попробовал следующее:
class C2 {
constexpr static int foo(int x) { return x + 1; }
constexpr static int bar;
};
constexpr int C2::bar = C2::foo(sizeof(int));
g++-4.5.3
будет компилировать это без жалоб. К сожалению, в моем другом коде используются циклы for
на основе диапазона, поэтому у меня должно быть не менее 4,6. Теперь, когда я смотрю ближе к списку поддержки, кажется, что constexpr
потребуется также 4.6. И с g++-4.6.3
я получаю
3:24: error: constexpr static data member ‘bar’ must have an initializer
5:19: error: redeclaration ‘C2::bar’ differs in ‘constexpr’
3:24: error: from previous declaration ‘C2::bar’
5:19: error: ‘C2::bar’ declared ‘constexpr’ outside its class
5:19: error: declaration of ‘const int C2::bar’ outside of class is not definition [-fpermissive]
Это звучит очень странно для меня. Как вещи "отличаются в constexpr
" здесь? Мне не хочется добавлять -fpermissive
, поскольку я предпочитаю, чтобы мой другой код был тщательно проверен. Перемещение реализации foo
вне тела класса не имело видимого эффекта.
Ожидаемые ответы
Может кто-нибудь объяснить, что здесь происходит? Как я могу достичь того, что я пытаюсь сделать? Меня в основном интересуют ответы следующих типов:
- Способ выполнения этой работы в gcc-4.6
- Наблюдение, что более поздние версии gcc могут иметь дело с одной из версий правильно
- Указатель на спецификацию, согласно которой, по крайней мере, одна из моих конструкций должна работать, так что я могу обмануть разработчиков gcc, чтобы заставить их работать.
- Информация о том, что то, что я хочу, невозможно в соответствии со спецификациями, предпочтительно с некоторым подтверждением относительно обоснования этого ограничения.
Другие полезные ответы также приветствуются, но, возможно, они не будут приняты так же легко.
Ответы
Ответ 1
Стандарт требует (раздел 9.4.2):
A static
член данных типа literal может быть объявлен в определении класса с помощью спецификатора constexpr
; если это так, в его декларации указывается логический или равный-инициализатор, в котором каждое предложение-инициализатор, являющееся выражением-присваивания, является постоянным выражением.
В вашей "второй попытке" и в ответе Илии в декларации нет элемента с выравниванием или равным.
Ваш первый код верен. К сожалению, gcc 4.6 не принимает его, и я не знаю, где угодно, чтобы удобно попробовать 4.7.x(например, ideone.com все еще застрял на gcc 4.5).
Это невозможно, потому что, к сожалению, стандарт не позволяет инициализировать статический элемент данных constexpr
в любом контексте, где класс завершен. Специальное правило для атрибутов с выравниванием/равными в 9.2p2 применяется только к элементам нестатических, но это статическое.
Наиболее вероятной причиной этого является то, что переменные constexpr
должны быть доступны как константы постоянной времени компиляции изнутри тел функций-членов, поэтому инициализаторы переменных полностью определены перед телами функции, что означает, что функция все еще неполна (undefined) в контексте инициализатора, а затем это правило срабатывает, делая выражение не постоянным выражением:
вызов функции undefined constexpr
или конструктора undefined constexpr
вне определения функции constexpr
или конструктора constexpr
;
Рассмотрим:
class C1
{
constexpr static int foo(int x) { return x + bar; }
constexpr static int bar = foo(sizeof(int));
};
Ответ 2
1). Пример Ilya должен быть недопустимым кодом на основе того факта, что строка элемента static constexpr datastrong > инициализируется вне линии, нарушая следующую инструкцию в стандарте:
9.4.2 [class.static.data] p3:... Статический член данных типа literal может быть объявлен в определении класса с помощью спецификатора constexpr; если это так, в его декларации указывается выравнивающий или равный-инициализатор в который каждый оператор-инициализатор, являющийся выражением присваивания, является постоянное выражение.
2) Код в вопросе MvG:
class C1 {
constexpr static int foo(int x) { return x + 1; }
constexpr static int bar = foo(sizeof(int));
};
действителен, насколько я вижу и интуитивно ожидал, что он будет работать, потому что статический член foo (int) определяется временной обработкой запуска бара (при условии обработки сверху вниз).
Некоторые факты:
- Я согласен с тем, что класс C1 не является полным в точке вызова foo (на основе 9.2p2) , но полнота или неполнота класса C1 ничего не говорит о том, определена ли foo как далеко как стандарт.
- Я искал стандарт для определения функций-членов, но ничего не нашел.
- Итак, утверждение, упомянутое Беном, здесь не применяется, если моя логика верна:
вызов функции undefined constexpr или undefinedconstexpr вне определения функции constexpr или конструктор constexpr;
3) Последний пример, приведенный Беном, упрощен:
class C1
{
constexpr static int foo() { return bar; }
constexpr static int bar = foo();
};
выглядит недействительным, но по разным причинам, а не просто потому, что foo вызывается в инициализаторе строки. Логика выглядит следующим образом:
- foo() вызывается в инициализаторе статической строки constexpr, поэтому он должен быть постоянным выражением (на 9.4.2 p3).
- так как он вызывает функцию constexpr, подменяет вызов функции (7.1.5 p5).
- Они не являются параметрами для функции, поэтому в левом заключается "неявное преобразование полученного возвращаемого выражения или файла braced-init-list в возвращаемый тип функции, как если бы это была операция копирования". (7.1.5 p5)
- выражение return - это просто bar, который является lvalue и требуется преобразование lvalue-to-rval.
-
но с помощью пули 9 в (5.19 p2), которая не соответствует не, потому что она еще не инициализирована:
- преобразование lvalue-to-rvalue (4.1), если оно не применяется к:
- значение целочисленного или перечисляемого типа, которое относится к энергонезависимому объекту const с предшествующей инициализацией, инициализированным константным выражением.
-
следовательно, преобразование lvalue-to-r bar не приводит к тому, что константное выражение не удовлетворяет требованию в (9.4.2 p3).
- поэтому по ошибке 4 в (5.19 p2) вызов foo() не является постоянным выражением:
вызов функции constexpr с аргументами, которые при замене подстановкой вызова функции (7.1.5) не создают константное выражение
Ответ 3
#include <iostream>
class C1
{
public:
constexpr static int foo(constexpr int x)
{
return x + 1;
}
static constexpr int bar;
};
constexpr int C1::bar = C1::foo(sizeof(int));
int main()
{
std::cout << C1::bar << std::endl;
return 0;
}
Такая инициализация работает хорошо, но только на clang
Ответ 4
Вероятно, проблема здесь связана с порядком декларации/определений в классе. Как вы все знаете, вы можете использовать любой член еще до того, как он будет объявлен/определен в классе.
Когда вы определяете значение constexpr в классе, компилятор не имеет функции constexpr, доступной для использования, потому что она находится внутри класса.
Возможно, ответ Philip, связанный с этой идеей, является хорошим моментом для понимания вопроса.
Обратите внимание на этот код, который компилируется без проблем:
constexpr int fooext(int x) { return x + 1; }
struct C1 {
constexpr static int foo(int x) { return x + 1; }
constexpr static int bar = fooext(5);
};
constexpr static int barext = C1::foo(5);