если constexpr с static_assert в лямбде, какой компилятор правильный?

13

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

Следующий код компилируется с помощью gcc, но 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)>.

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

Флорестан
источник
Здесь задается тот же вопрос, но с противоположной стороны: stackoverflow.com/questions/59393908/… .
Натан Оливер
1
@mfnx Не могу воспроизвести . Можете поделиться примером?
Натан Оливер
2
Я бы сказал, что оба правы (плохо сформированный NDR): static_assert(False<int>, "AAA");эквивалентно static_assert(false, "AAA");внутри лямбда.
Jarod42
2
@mfnx Вы изменили значение константы. Использование примера OP, где константой является f(std::integral_constant<int, 1>{});Wandbox, не вызывает утверждения: wandbox.org/permlink/UFYAmYwtt1ptsndr
NathanOliver
1
@NathanOliver Да, вы правы, извините за шум. Кажется, gcc как раз прав, поскольку этот код в constexpr следует отбросить, если константа> = 0;
mfnx

Ответы:

1

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

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

Читая, что можно подумать, что статическое утверждение будет удалено, но это не так.

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

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

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

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

[...]

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

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

Компилятор может видеть, что для любого экземпляра шаблона operator()статическое утверждение всегда будет срабатывать для текущего 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>{});
}

Живой пример

Гийом Расико
источник
13

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

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

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

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

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

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

Барри
источник