Почему не могут складываться выражения в постоянном выражении?
Рассмотрим следующий код:
template<int value>
constexpr int foo = value;
template<typename... Ts>
constexpr int sum(Ts... args) {
return foo<(args + ...)>;
}
int main() {
static_assert(sum(10, 1) == 11);
}
clang 4.0.1 дает мне следующую ошибку:
main.cpp:6:17: error: non-type template argument is not a constant expression
return foo<(args + ...)>;
^~~~
Это меня удивило. Каждый аргумент известен во время компиляции, sum
помечен как constexpr
, поэтому я не вижу причин, по которым выражение fold не может быть оценено во время компиляции.
Естественно, это также выходит из строя с тем же сообщением об ошибке:
constexpr int result = (args + ...); // in sum
[expr.prim.fold]
не очень полезно, он очень короткий и описывает только разрешенный синтаксис.
Попытка новых версий clang также дает тот же результат, что и gcc.
Действительно ли они разрешены или нет?
Ответы
Ответ 1
Постоянному выражению разрешено содержать выражение сгиба. Нельзя использовать значение параметра функции, если вызов функции не является частью всего константного выражения. В качестве примера:
constexpr int foo(int x) {
// bar<x>(); // ill-formed
return x; // ok
}
constexpr int y = foo(42);
Переменная y
должна быть инициализирована константным выражением. foo(42)
является приемлемым константным выражением, потому что хотя вызов foo(42)
включает в себя выполнение преобразования lvalue-to-rvalue в параметре x
, чтобы вернуть его значение, этот параметр был создан во всем постоянном выражении foo(42)
, поэтому его значение статически известно. Но x
сам по себе не является постоянным выражением внутри foo
. Выражение, которое не является постоянным выражением в контексте, где оно происходит, тем не менее может быть частью большего постоянного выражения.
Аргумент параметра шаблона непигового типа должен быть константным выражением сам по себе, но x
не является. Таким образом, прокомментированная строка плохо сформирована.
Аналогично, ваш (args + ...)
не может быть постоянным выражением (и, следовательно, не может использоваться как аргумент шаблона), поскольку он выполняет преобразование lvalue-to-rval по параметрам sum
. Однако, если функция sum
вызывается с постоянными аргументами выражения, вызов функции в целом может быть постоянным выражением, даже если внутри него появляется (args + ...)
.
Ответ 2
Некоторым читателям этого вопроса может быть интересно узнать, как пример OP: s может быть изменен для компиляции и запуска, как ожидалось, поэтому я включаю это добавление в Brian: s отличный принятый ответ.
Как описывает Брайан, значение параметра вариационной функции не является постоянным выражением внутри sum
(но не приведет к тому, что foo
не будет постоянным выражением, пока параметр не "убежит" в области of foo
, поскольку он был создан в пределах постоянного выражения foo(42)
).
Чтобы применить это знание к примеру OP: вместо использования параметра вариационной функции, который не будет обрабатываться как constexpr
при выходе из constexpr
немедленного объема sum
, мы можем перенести параметр вариационной функции для параметра вариационного непигового шаблона.
template<auto value>
constexpr auto foo = value;
template<auto... args>
constexpr auto sum() {
return foo<(args + ...)>;
}
int main() {
static_assert(sum<10, 1, 3>() == 14);
}
Ответ 3
Ваша проблема не связана с ...
.
template<class T0, class T1>
constexpr int sum(T0 t0, T1 t1) {
return foo<(t0+t1)>;
}
это также терпит неудачу тем же способом.
Ваша проблема в том, что функция constexpr
должна быть вызвана с аргументами не constexpr
.
Это распространенное непонимание того, что означает constexpr
: это не означает "всегда constexpr
".
Существуют сложные стандартные положения, в которых говорится, что здесь не так, но суть в том, что внутри функции constexpr
сами аргументы функции не считаются constexpr
. Результат функции может быть, если входы есть, но внутри функции код должен быть действительным, даже если аргументы не constexpr
.
Вы все еще можете обойти это: пользователь определяет литерал ""_k
, который анализирует целое число и генерирует integral_constant
.
static_assert(sum(10_k, 1_k) == 11);
будет компилироваться и выполняться, потому что +
для интегральных констант не зависит от переменных constexpr
. Или вы можете принимать значения как параметры непигового шаблона. static_assert (сумма < 10, 1 > () == 11);