Выражение constexpr и время жизни переменной, пример, где g++ и clang не согласны

Рассмотрим простой код С++ 11:

template<int N>
struct Foo {};

template <int N>
constexpr int size(const Foo<N>&) { return N; }

template <int N>
void use_size(const Foo<N>& foo) { constexpr int n = size(foo); }

int main()
{
    Foo<5> foo;

    constexpr int x = size(foo);  // works with gcc and clang
                                  // _but_
    use_size(foo);                // the same statement in the use_size() 
                                  // function _only_ works for gcc
}

Я могу с успехом скомпилировать его с помощью g++ -std=c++11 foo.cpp

однако, если я использую clang++, clang++ -std=c++11 foo.cpp, я получаю

foo.cpp:15:28: error: constexpr variable 'n' must be initialized by a constant expression
void use_size(const Foo<N>& foo) { constexpr int n = size(foo); }
                                                     ~~~~~^~~~
foo.cpp:23:5: note: in instantiation of function template specialization 'use_size<5>' requested here
    use_size(foo);                // the same statement in the use_size() 
    ^
1 error generated.

(nb: версии компилятора. Я проверил предыдущий оператор с версией g++ версии 5.3.1 и 7.2.1 и с clang++ версии 3.6.2 и 5.0.0)

Мой вопрос:, который из g++ или clang прав? В чем проблема?

Ответ 1

Моя интерпретация заключается в том, что clang++ является правильным и g++ слишком разрешительным.

Мы можем найти близкий пример (раздел [expr.const], стр. 126) в стандартном https://isocpp.org/std/the-standard (черновик может быть загружен, внимание большого PDF!).

constexpr int g(int k) { 
    constexpr int x = incr(k); 

    return x; 
}

где объясняется, что:

error: incr (k) не является основным константным выражением, потому что время жизни k начиналось вне выражения incr (k)

Это именно то, что происходит в функции use_size() с аргументом foo, даже если функция size() только использует параметр шаблона N.

template <int N>
constexpr int size(const Foo<N>&) { return N; }

template <int N>
void use_size(const Foo<N>& foo) { constexpr int n = size(foo); }

Ответ 2

Я ожидал, что Клэнг окажется неправым в этом случае. Он должен оценивать ваш вызов функции как постоянное выражение, просто потому, что вы используете только параметр шаблона, а не сам объект. Поскольку вы не используете объект в своей функции constexpr, не должно быть ничего запретного для оценки времени компиляции.

Однако существует правило в стандарте, в котором говорится, что объект, который начал свое время жизни, предшествующее постоянному выражению, например ссылку, не может использоваться как constexpr.

В этом случае есть простое исправление. Я думаю, что это не понравилось ссылку:

template <int N> // pass by value, clang is happy
void use_size(Foo<N> foo) { constexpr int n = size(foo); }

Здесь живой пример

Кроме того, вы также можете скопировать свой объект foo и использовать этот локальный объект:

template <int N>
void use_size(const Foo<N>& foo) {
    auto f = foo;
    constexpr int n = size(f);
}

Пример в реальном времени