TL; DR: это определенное поведение в соответствии со стандартом.У вывода аргументов шаблона есть специальное правило для вывода аргументов шаблона при получении адреса шаблона функции, позволяющее пересылать ссылки для работы, как ожидалось.Для шаблонов функций преобразования такого правила не существует.
Примечание: похоже, это просто область, для которой еще никто не написал предложение.Если кто-то напишет предложение для этого, вполне вероятно, что это может быть сделано в будущем.
С [expr.прим.ламба] :
....Для обобщенной лямбды без лямбда-захвата тип замыкания имеет шаблон функции преобразования в указатель на функцию. Шаблон функции преобразования имеет тот же изобретенный список параметров шаблона, а указатель на функцию имеет те же типы параметров, что и шаблон оператора вызова функции. Тип возврата указателя на функцию должен вести себя так, как если бы он былспецификатор decltype, определяющий тип возврата соответствующей специализации шаблона оператора вызова функции.
выделение добавлено
Указывает, что аргументы шаблона и типы параметров функциидолжны быть скопированы в формате один к одному:
// simplified version of the example in [expr.prim.lambda]/8
struct Closure {
template <typename T>
void operator()(T&& t) const {
/* ... */
}
template <typename T>
static void lambda_call_operator_invoker(T&& t) {
Closure()(std::forward<T>(t));
}
// Exactly copying the template parameter list and function parameter types.
template <typename T>
using fn_type = void(*)(T&&);
// using fn_type = void(*)(T); // this compiles, as noted later
template <typename T>
operator fn_type<T>() const {
return &lambda_call_operator_invoker<T>;
}
};
Это не скомпилирует на все три из Clang, GCC и MSVC, , что, безусловно, может удивить, так как мыожидали, что свертка ссылок произойдет по аргументу T&&
.
Однако стандарт не поддерживает это.
Важные части стандарта [temp.deduct.funcaddr] (вывод аргументов шаблона с использованием адреса шаблона функции) и [temp.deduct.conv] (вывод аргументов шаблона функции преобразования).Критически, [temp.deduct.type] специально упоминает [temp.deduct.funcaddr] , но не [temp.deduct.conv] .
Некоторые термины, используемые в стандарте:
- P - тип возврата шаблона преобразования или тип шаблона функции
- A -тип, который мы «пытаемся преобразовать в»
Аналогично, если P имеет форму, содержащую (T), то каждый тип параметра P i соответствующегосписок типов параметров ([dcl.fct]) для P сравнивается с соответствующим типом параметра A i соответствующего списка типов параметров для A. Если P и A являются типами функцийкоторая возникла из дедукции при получении адреса шаблона функции ([temp.deduct.funcaddr]) или при выводе аргументов шаблона из объявления функции ([temp.deduct.decl]) и P i и A i - параметры списка типов параметров верхнего уровня для P и A, соответственно, P i является дополнительнымsted, если это ссылка на пересылку ([temp.deduct.call]) и A i является ссылкой lvalue, и в этом случае тип P i изменяется на шаблонтип параметра (т. е. T&&
изменяется на просто T
).
выделение добавлено
Это специально вызывает взятие адресашаблона функции, делая переадресацию ссылок просто работать.Нет аналогичной ссылки на шаблоны функций преобразования.
Повторное рассмотрение примера ранее, если мы изменим fn_type
на void(*)(T)
, то есть та же самая операция, которая описана здесь в стандарте.