Подтвердить что ты не робот

Почему не могут складываться выражения в постоянном выражении?

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

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.

Действительно ли они разрешены или нет?

4b9b3361

Ответ 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);