Подтвердить что ты не робот

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, чтобы заставить их работать.
  • Информация о том, что то, что я хочу, невозможно в соответствии со спецификациями, предпочтительно с некоторым подтверждением относительно обоснования этого ограничения.

Другие полезные ответы также приветствуются, но, возможно, они не будут приняты так же легко.

4b9b3361

Ответ 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 data​​strong > инициализируется вне линии, нарушая следующую инструкцию в стандарте:

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);