Вопрос или проблема
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++ и спецификаций компиляторов.