Инициализация статического члена данных constexpr базового класса с использованием статического члена данных constexpr производного класса

Рассмотрим следующий код:

template<typename T>
struct S { static constexpr int bar = T::foo; };

struct U: S<U> { static constexpr int foo = 42; };

int main() { }

GCC v6.1 компилирует его, clang 3.8 отклоняет его с ошибкой:

2: ошибка: ни один член с именем 'foo' в 'U'
struct S {static constexpr int bar = T:: foo; };

Какой компилятор прав?
Это может быть связано с тем, что U не является полным типом в точке, где мы пытаемся использовать его в S? < ш > В этом случае его следует рассматривать как ошибку GCC, но я хотел бы знать, буду ли я раньше, чтобы искать/открывать проблему в трекере ошибок...

ИЗМЕНИТЬ

Между тем я открыл bug для GCC.
Ожидая ответа на этот вопрос.

Ответ 1

Для С++ 14 и 11 Clang прав; однако в последнем рабочем проекте (будущее С++ 17) все изменилось - см. следующий раздел.

Стандартные котировки для поиска (от N4140, проект ближе всего к С++ 14):

[temp.inst]/1:

[...] Неявное инстанцирование специализации шаблона класса вызывает неявное создание объявлений, но не определения, аргументы по умолчанию или спецификации исключения функции-члены класса, классы-члены, узкоспециализированные перечисления, статические элементы данных и шаблоны членов; [...]

[temp.point]/4:

Для специализации шаблона класса [...] точка инстанцирования поскольку такая специализация сразу предшествует области пространства имен декларация или определение, относящееся к специализации.

Итак, точка инстанцирования для S<U> находится прямо перед объявлением U, только с передним объявлением struct U;, предварительно введенным ранее, так что будет найдено имя U.

[class.static.data]/3:

[...] Статический член данных типа literal может быть объявлен в определение класса с помощью спецификатора constexpr; если да, то в декларации указывается логический или равный-инициализатор, в котором каждое предложение-инициализатор, являющееся выражением присваивания, является постоянное выражение. [...] Член должен быть определен в область пространства имен, если она используется в odr (3.2) в программе и определение области пространства имен не должно содержать инициализатор.

В соответствии с приведенным выше параграфом декларация bar в определении S, даже если она имеет инициализатор, по-прежнему является просто объявлением, а не определением, поэтому она создается, когда S<U> неявно экземпляр, и там нет U::foo в это время.

Обходной путь состоит в том, чтобы сделать bar функцией; согласно первой цитате, определение функции не будет создаваться во время неявного экземпляра S<U>. Пока вы используете bar после того, как было определено определение U (или изнутри тел других функций-членов S), поскольку они, в свою очередь, будут создаваться отдельно отдельно, когда это необходимо - [14.6. 4.1p1]), что-то вроде этого будет работать:

template<class T> struct S 
{
   static constexpr int bar() { return T::foo; }
};

struct U : S<U> { static constexpr int foo = 42; };

int main()
{
   constexpr int b = U::bar();
   static_assert(b == 42, "oops");
}

После принятия P0386R2 в рабочий проект (в настоящее время N4606), [class.static.data]/3 были изменены; соответствующая часть теперь читает:

[...] Встроенный элемент статических данных может быть определен в определении класса и может указывать логический или равный-инициализатор. Если член объявленный с помощью спецификатора constexpr, он может быть переоформлен в область пространства имен без инициализатора (это использование устарело, см. D.1). [...]

Это дополняется изменением на [basic.def]/2.3:

Декларация - это определение, если: [...]

  • он объявляет не-встроенный элемент статических данных в определении класса (9.2, 9.2.3),

[...]

Итак, если он встроен, это определение (с инициализатором или без него). И [dcl.constexpr]/1 говорит:

[...] Функция или элемент статических данных, объявленный с помощью constexprСпецификатор неявно является встроенной функцией или переменной (7.1.6). [...]

Это означает, что объявление bar теперь является определением, и в соответствии с кавычками в предыдущем разделе он не создается для неявного экземпляра S<U>; в это время создается только объявление bar, которое не включает инициализатор.

Изменения в этом случае хорошо представлены в примере в [des.static_constexpr] в текущем рабочем черновике:

struct A {
   static constexpr int n = 5; // definition (declaration in C++ 2014)
};

const int A::n; // redundant declaration (definition in C++ 2014)

Это делает поведение GCC стандартно-совместимым в режиме С++ 1z.