если constexpr с static_assert в лямбде, какой компилятор правильный? - PullRequest
13 голосов
/ 08 января 2020

Когда мы хотим использовать static_assert в if constexpr, мы должны сделать условие зависимым от некоторого параметра шаблона. Интересно, что g cc и clang не соглашаются, когда код заключен в лямбду.

Следующий код компилируется с g cc, но clang вызывает утверждение, даже если if constexpr не может быть правда.

#include <utility>

template<typename T> constexpr std::false_type False;

template<typename T>
void foo() {

    auto f = [](auto x) {
        constexpr int val = decltype(x)::value;
        if constexpr(val < 0) {
            static_assert(False<T>, "AAA");
        }
    };

    f(std::integral_constant<int, 1>{});
}

int main() {
    foo<int>();
}

Живой пример здесь .

Это можно легко исправить, заменив False<T> на False<decltype(x)>.

Итак, вопрос в том, какой компилятор прав? Я бы предположил, что g cc является правильным, потому что условие в static_assert зависит от T, но я не уверен.

Ответы [ 2 ]

13 голосов
/ 08 января 2020

Обычное правило здесь: [temp.res] / 8 :

Программа некорректна, диагностика не требуется c требуется, если: нет действительной специализации может быть сгенерировано для шаблона или подстановки constexpr, если оператор внутри шаблона и экземпляр не создан

После создания экземпляра foo<T> ваш static_assert больше не зависит. Он становится static_assert(false) - для всех возможных реализаций оператора вызова родового c лямбда f. Это неправильно, никакой диагностики c не требуется. Clang диагностирует, g cc нет. Оба являются правильными.

Обратите внимание, что не имеет значения, что static_assert здесь отбрасывается .

Это можно легко исправить, заменив False<T> на False<decltype(x)>.

Это сохраняет зависимость static_assert в лямбде-генерике c, и теперь мы попадаем в состояние, в котором гипотетически может быть действительная специализация, поэтому мы больше не плохо сформированы, NDR.

1 голос
/ 08 января 2020

С [stmt.if] / 2 (выделение мое)

Если оператор if имеет форму if constexpr, значение условия должно быть контекстуально преобразованное константное выражение типа bool; эта форма называется оператором constexpr. Если значение преобразованного условия равно false, первое подзаголовок является отклоненным оператором, в противном случае второе подзаголовок, если имеется, является отброшенным оператором. Во время создания экземпляра вмещающего шаблонного объекта ([temp.pre]), если условие не зависит от значения после его создания, исключенное подзаголовок (если есть) не создается.

Считая, что можно было бы предположить, что утверждение * stati c будет отброшено, но это не так.

Утверждение stati c запускается на первом этапе шаблона, поскольку компилятор знаю, что это всегда ложь.

С [temp.res] / 8 (выделение мое)

Достоверность шаблона может быть проверена перед любой реализацией , [ Примечание: Зная, какие имена являются именами типов, можно таким образом проверять синтаксис каждого шаблона. - примечание к концу ] Программа некорректна, диагностика не требуется c, если:

  • (8.1) для шаблона не может быть сгенерирована действительная специализация или подстановка оператора constexpr if в шаблоне и шаблоне не создается , или

[...]

Да, действительно, ваш False<T> зависит от T. Проблема в том, что обобщенная c лямбда сама является шаблоном, а False<T> не зависит от какого-либо параметра шаблона лямбды.

Для T, что False<T> ложно, c assert всегда будет ложным, независимо от того, какой аргумент шаблона отправляется лямбда-выражению.

Компилятор может видеть, что для любого экземпляра шаблона operator(), stati c assert всегда срабатывает для текущего T. Следовательно, ошибка компилятора.

Решение для этого будет зависеть от x:

template<typename T>
void foo() {

    auto f = [](auto x) {
        if constexpr(x < 0) {
            static_assert(False<decltype(x)>, "AAA");
        }
    };

    f(std::integral_constant<int, 1>{});
}

Живой пример

...