Почему gcc не работает при использовании лямбда для параметра шаблона без типа?
Следующий фрагмент компилирует без ошибок с Clang 4.0, но GCC 7.0 производит (обратите внимание на использование -std = С++ 1z flag).
using FuncT = int (*)(double);
template <FuncT FUNC>
int temp_foo(double a)
{
return FUNC(a);
}
int foo(double a)
{
return 42;
}
void func()
{
auto lambda = [](double a) { return 5; };
struct MyStruct
{
static int foo(double a) { return 42; }
};
temp_foo<foo>(3);
temp_foo<static_cast<FuncT>(lambda)>(3);
temp_foo<MyStruct::foo>(3);
}
В частности, GCC жалуется, что как лямбда, так и метод вложенного класса не имеют привязки, поэтому они не могут использоваться как аргумент шаблона непигового типа.
По крайней мере, для лямбда-случая я считаю, что Clang правильный (и GCC ошибочен), поскольку (цитируя cppreference, оператора преобразования):
Значение, возвращаемое этой функцией преобразования, является указателем на функция с связью языка С++, которая при вызове имеет тот же эффект как вызов оператора вызова функции закрытия объекта непосредственно.
Неправильное поведение GCC?
Ответы
Ответ 1
Согласно http://en.cppreference.com/w/cpp/language/template_parameters#Non-type_template_parameter, похоже, что внешняя связь больше не является требованием с С++ 17. Тот же язык найден в черновике С++ 17 в [temp.arg.nontype] на http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf (обратите внимание, что это неправильно связанный как проект С++ 14).
Аргумент шаблона, который может использоваться с параметром шаблона непигового типа, может быть любым преобразованным константным выражением типа параметра шаблона...
Единственные исключения состоят в том, что параметры шаблона без указания типа ссылки и типа указателя не могут ссылаться на/быть адресом
- подобъект (включая нестатический член класса, базовый подобъект или массив элемент);
- временный объект (в том числе созданный во время инициализации ссылки);
- строковый литерал;
- результат typeid;
- или предопределенная переменная __func __.
Эта ссылка на cppreference также специально упоминает указатели на функции, pre С++ 17:
При создании шаблонов с параметрами шаблона непигового типа применяются следующие ограничения:
...
Для указателей на функции допустимыми аргументами являются указатели на функции с привязкой (или константные выражения, которые оценивают значения нулевого указателя).
Так как ваш вопрос помечен С++ 1z (мы должны, вероятно, иметь 17 тегов к настоящему моменту и использовать это вместо 17), мы должны использовать первый набор правил. Ваш пример, похоже, не попадает ни в одну из категорий исключений для С++ 17, и поэтому gcc находится в ошибке.
Обратите внимание, что clang не компилирует ваш пример, если вы меняете флаг языка на 14.
Ответ 2
Я согласен с ответом Нира и хотел бы добавить к нему некоторую информацию. Он ссылается на соответствующий раздел в стандарте (§14.3.2 [temp.arg.nontype]), который показывает, что больше не требуется, чтобы параметры не-типа имели привязку, но это все еще не показывает, что GCC плохо себя ведет для лямбды. Для этого нам нужно показать, что static_cast<FUNCT>(lambda)
- преобразованное константное выражение. Для этого нам нужен новый проект из одного связанного Nir. И этот раздел
§5.1.5 Лямбда-выражения [expr.prim.lambda]:
- Тип замыкания для не-общего лямбда-выражения без лямбда-захвата имеет функцию преобразования для указателя на функцию с С++ языковая связь (7.5), имеющая одинаковые параметры и возвращаемые типы так как оператор вызова функции замыкания. [...] Преобразование функция [...] является общедоступной, constexpr, не виртуальный, не явный, const, и имеет исключение без бросания спецификация.
Интересно, что GCC утверждает, что уже выполнил это (N4268) в уже выпущенной версии 6 (в случае, если вы хотите оправдать поведение GCC, заявив, что GCC 7 официально не выпущен, так что, возможно, когда это выйдет, это будет исправлено):
Language Feature Proposal Available in GCC? SD-6 Feature Test
Allow constant evaluation for all non-type template arguments N4268 6 __cpp_nontype_template_args >= 201411
Итак, в общем, это ошибка в GCC.