Функция static constexpr`, вызываемая в постоянном выражении, является... ошибкой?

У меня есть следующий код:

class MyClass
{
  static constexpr bool foo() { return true; }
  void bar() noexcept(foo()) { }    
};

Я бы ожидал, что поскольку foo() является функцией static constexpr, и поскольку он определен до объявления bar, это было бы вполне приемлемо.

Однако g++ дает мне следующую ошибку:

 error: ‘static constexpr bool MyClass::foo()’ called in a constant expression

Это... менее полезно, так как возможность вызова функции в постоянном выражении является всей точкой constexpr.

clang++ немного полезнее. В дополнение к сообщению об ошибке, в котором утверждается, что аргумент noexcept должен быть постоянным выражением, он говорит:

note: undefined function 'foo' cannot be used in a constant expression
note: declared here
static constexpr bool foo() { return true; }
                      ^

Итак... это проблема с двумя проходами? Проблема в том, что компилятор пытается объявить все функции-члены в классе до того, как какой-либо из них будет определен? (Обратите внимание, что вне контекста класса ни один компилятор не выдает ошибку.) Это меня удивляет; интуитивно, я не вижу причин, по которым функции-члены static constexpr не могут использоваться в любых и всех постоянных выражениях внутри класса или вне.

Ответы

Ответ 1

Как T.C. продемонстрированный с некоторыми ссылками в комментарии, стандарт не совсем ясен по этому поводу; аналогичная проблема возникает с возвратными типами возврата с помощью decltype(memberfunction()).

Центральная проблема заключается в том, что члены класса обычно не считаются объявленными до тех пор, пока класс, в котором они объявлены, не будет завершен. Таким образом, независимо от того, что foo является static constexpr, и его объявление предшествует значению bar, оно не может считаться "доступным" для использования в постоянном выражении до тех пор, пока MyClass не будет завершено.

Как отмеченный Шафиком Ягмуром, в рамках стандарта есть некоторая попытка избежать зависимости от упорядочения членов внутри класса и, очевидно, исходный вопрос для компиляции приведет к упорядочивающей зависимости (поскольку foo нужно будет объявить до bar). Тем не менее, уже есть незначительная зависимость от упорядочения, потому что хотя функции constexpr нельзя вызывать внутри noexcept, само выражение noexcept может зависеть от более раннего объявления внутри класса:

class MyClass
{
    // void bar() noexcept(noexcept(foo())); // ERROR if declared here
    static constexpr bool foo();
    void bar() noexcept(noexcept(foo())); // NO ERROR
}

(Обратите внимание, что это фактически не является нарушением 3.3.7, так как здесь возможно только одна правильная программа.)

Такое поведение может фактически являться нарушением стандарта; Агар Т.С. указывает (в комментарии ниже), что foo здесь действительно нужно искать в рамках всего класса. И g++ 4.9.2, и clang++ 3.5.1 сбой при ошибке, когда bar объявляется первым, но компилируется без ошибок или предупреждений, когда foo объявляется первым. EDIT: clang++ trunk-revision 238946 (от незадолго до выпуска 3.7.0) не сбой, если bar объявлен первым; g++ 5.1 все еще не работает.

Интригующе, следующая вариация вызывает "другой спецификатор исключения" с clang++, но не с g++:

class MyClass
{
  static constexpr bool foo2();
  void bar2() noexcept(noexcept(foo2()));
};

constexpr bool MyClass::foo2() { return true; }
void MyClass::bar2() noexcept(noexcept(MyClass::foo2())) { }

В соответствии с ошибкой спецификация noexcept для объявления bar2 оценивается как noexcept(false), которая затем считается несоответствием для noexcept(noexcept(MyClasss::foo2())).