g++ и clang++ различное поведение с рекурсивной инициализацией статического члена

Учитывая следующий код:

#include <iostream>

template <std::size_t N>
struct foo
 { static std::size_t value; };

template <>
std::size_t foo<0>::value = 0u;

template <size_t N>
std::size_t foo<N>::value = 1u + foo<N - 1u>::value;

int main()
 {
   std::cout
      << foo<3u>::value << ' '
      << foo<2u>::value << ' '
      << foo<1u>::value << ' '
      << foo<0u>::value << std::endl;
 }

где статический член value из шаблона структуры foo рекурсивно инициализирован, я получаю различные выходы из g++:

3 2 1 0

и из клана g++:

1 1 1 0

Поэтому кажется, что g++ рекурсивно инициализирует foo<N>::value используя инициализированное значение foo<N-1u>::value где клан g++ использует ноль для foo<N-1u>::value.

Два вопроса:

  1. законен ли предыдущий код или это неопределенное поведение в некотором роде?
  2. если предыдущий код является законным, кто прав: g++ или клан g++?

Ответ 1

Это не указано. Оба компилятора правы.

Вот соответствующие части из cppreference "инициализация".

Статическая инициализация

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

Поэтому для всех этих переменных они равны нулю при загрузке программы. Затем:

Динамическая инициализация

После завершения статической инициализации динамическая инициализация нелокальных переменных происходит в следующих ситуациях:

1) Неупорядоченная динамическая инициализация, которая применяется только к (статическим/локальным для потока) членам статических данных шаблона класса и... которые не являются явно специализированными.

И эти переменные соответствуют критериям. И тогда это говорит:

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

Это означает, что любая последовательность инициализации в порядке. Оба компилятора верны.

Чтобы избежать этой проблемы, используйте constexpr для принудительной "постоянной инициализации".

Ответ 2

Это не указано.

Вы используете конструкцию, где вы ссылаетесь на определение переменной на себя - возможно, в некоторой степени аналогично произнесению int я = i-1. В случае clang, это просто использование общего определения шаблона

template <std::size_t N>
struct foo
  { static std::size_t value; };//without specialization this will be ZERO initialized

потому что он не видел "себя", как обычный шаблонный класс или функция (в отличие от случая gcc).

Подводить итоги:

1) легит

2) не указано

Чтобы избежать проблем, используйте и вместо этого специализируйте шаблон класса.