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

Вопрос или проблема

GCC принимает этот код, а Clang и MSVC отклоняют его из-за неудачной статической проверки в assert. Что говорит стандарт?

https://godbolt.org/z/PKMKzYGsc

template<typename T>
constexpr int assert() {
    static_assert(sizeof(T) == 1);
    return 1;
}

template<auto I>
using Int = int;

template<typename T> requires (sizeof(T) == 1)
constexpr auto foo(T) -> Int<assert<T>()> {
    return 1;
}

template<typename T> requires (sizeof(T) > 1)
constexpr auto foo(T a) -> Int<1> {
    return 2;
}

static_assert(foo('1') == 1);
static_assert(foo(2) == 2);

Вывод Clang:

<source>:3:19: ошибка: статическая проверка не удалась из-за требования 'sizeof(int) == 1'
    3 |     static_assert(sizeof(T) == 1);
      |                   ^~~~~~~~~~~~~~
<source>:11:30: примечание: при инстанцировании специализации шаблона функции 'assert<int>' запрос осуществляется здесь
   11 | constexpr auto foo(T) -> Int<assert<T>()> {
      |                              ^
<source>:21:15: примечание: при подстановке выведенных аргументов шаблона в шаблон функции 'foo' [с T = int]
   21 | static_assert(foo(2) == 2);
      |               ^
<source>:3:29: примечание: выражение вычисляется как '4 == 1'
    3 |     static_assert(sizeof(T) == 1);

Программа корректна и gcc прав в принятии данного кода, как объяснено ниже.

Из over.over:

Все функции с сопутствующими ограничениями, которые не удовлетворяются ([temp.constr.decl]), исключаются из набора выбранных функций.
Если в наборе остается более одной функции, все специализированные шаблоны функций в наборе исключаются, если набор также содержит функцию, которая не является специализированным шаблоном функции.
Любая данная функция, не являющаяся шаблоном, F0 исключается, если в наборе содержится вторая функция, не являющаяся шаблоном, которая более ограничена, чем F0 в соответствии с правилами частичного порядка [temp.constr.order].
Любая данная специализация шаблона функции F1 исключается, если в наборе содержится вторая специализация шаблона функции, шаблон которой более специализирован, чем шаблон функции F1 в соответствии с правилами частичного порядка [temp.func.order].
После таких исключений, если они имеются, должно остаться ровно одно выбранное действие.

(здесь выделено мной)

Это означает, что для вызова foo(2) версия #1 с sizeof(T)==1 будет исключена из набора выбранных функций. А другая версия (версия #2), которая удовлетворяет ограничению, станет единственным подходящим кандидатом и будет использована.

Ответ или решение

Проблема, рассматриваемая в коде, связана с выполнением статической проверки на размер типа T в функции assert. Этот код компилируется в GCC, но не в Clang и MSVC из-за неудачной статической проверки, и выводы этих компиляторов поднимают вопрос о соответствии стандарту C++.

Ваша программа действительно является корректной с точки зрения стандарта C++. Ключевым моментом является то, что функции с неподходящими ограничениями будут исключены из набора выбранных функций. Это правило изложено в разделе [over.over] (overloading resolution) стандарта C++, который ясно указывает, что:

Все функции с ассоциированными ограничениями, которые не удовлетворены, будут исключены из набора выбранных функций.

В вашем случае, для вызова foo(2), версия функции foo, где применяется sizeof(T) == 1, будет исключена из набора, поскольку для T равного int ограничение не выполняется. В результате остается только версия функции, которая удовлетворяет ограничению, а именно, версия с фиксированным возвращаемым значением Int<1>.

Таким образом, компилятор GCC правильно принимает код, так как он корректно распознает, что для foo(2) должен использоваться лишь тот перегруженный вариант, который соответствует требованиям, то есть версия, в которой T имеет размер больше чем 1.

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

Таким образом, согласно стандарту, поведение GCC является ожидаемым и корректным, и ваша программа действительно является хорошо сформированной. Чтобы избежать путаницы между компиляторами при работе с такими случаями, важно всегда проверять соблюдение требований стандартов C++ и спецификаций компиляторов.

Оцените материал
Добавить комментарий

Капча загружается...