Учитывая следующие две перегрузки
template<class T_ret, class... Args>
int test(T_ret (*func)(Args...)) { return 1; }
template<typename T>
int test(const T &lambda) { return 2; }
есть разница в поведении между MSVC и gcc /clang при передаче функции со спецификатором noexcept.
В следующем примере:
void func() noexcept {}
int main()
{
return test(&func);
}
gcc и clang принимают вторую перегрузку, а MSVC — первую перегрузку (https://godbolt.org/z/eah3r7E8r). Более конкретная перегрузка, добавляющая спецификатор noexcept, обнаруживается всеми компиляторами:
template<class T_ret, class... Args>
int test(T_ret (*func)(Args...) noexcept) { return 3; }
Какое разрешение перегрузки является правильным в данном случае?
Это зависит от того, какую версию С++ вы используете.
До C++17 noexcept
не был частью типа функции или типа указателя функции. Следовательно, обе функции могут принимать аргументы без какого-либо преобразования типов, а частичное упорядочение шаблонов функций разорвет связь в разрешении перегрузок с первой перегрузкой, которая является более специализированной (она принимает только указатели на функции, а вторая перегрузка принимает любой тип).
Поскольку C++17 noexcept
является частью типа функции и типа указателя функции, а преобразование, которое удаляет noexcept
, является преобразованием без удостоверения. Ваша первая перегрузка не имеет квалификатора noexcept
для типа указателя функции в параметре и, следовательно, требует этого преобразования, что делает его худшим выбором при ранжировании последовательностей преобразования, чем последовательность преобразования идентификаторов, необходимая для второй перегрузки.
MSVC по умолчанию использует какой-то стандарт до С++ 17, в то время как последние GCC по умолчанию используют С++ 17, отсюда и разница в вашем тесте проводника компилятора. Они соглашаются, если вы действительно устанавливаете версию.