Выражение 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);
}
Пример в реальном времени