Преобразование пересылки lambda в указатель функции

Вот две вещи, которые работают. Мы можем создать экземпляр шаблона функции пересылки, чтобы получить указатель на функцию lvalue:

template <class T>
void f(T &&) {}

void(*p)(int &) = f; // Cool!

Мы также можем преобразовать невостребованную общую лямбду с lvalue в указатель функции, принимающий lvalue:

auto l = [](auto &) { };

void (*lp)(int &) = l; // Still cool!

Но, по-видимому, ни один из GCC и Clang не преобразует обобщенную lambda-пересылку в указатель функции, принимающий lvalue:

auto l = [](auto &&) { };

void (*lp)(int &) = l; // Not cool!

Выходы GCC:

<source>:9:21: error: invalid user-defined conversion from '<lambda(auto:1&&)>' to 'void (*)(int&)' [-fpermissive]
 void (*lp)(int &) = l;
                     ^

Выходы Clang:

<source>:9:8: fatal error: no viable conversion from '(lambda at <source>:7:10)' to 'void (*)(int &)'
void (*lp)(int &) = l;
       ^            ~
<source>:7:10: note: candidate template ignored: could not match 'type-parameter-0-0 &&' against 'int &'
auto l = [](auto &&) { };
         ^

Это все, несмотря на то, что указатель функции-члена, принимающий lvalue, может быть получен из пересылки lambda:

auto lmp = &decltype(l)::operator()<int &>;

template <class...>
struct check;
check<decltype(lmp)> c;

... который выводит тип void (<lambda(auto:1&&)>::*)(int&) const как ожидалось.

Я считал, что правила обращения коллапса были присущи любому экземпляру шаблона и ожидали, что это сработает. У обоих Clang и GCC есть ошибка, или это фактически не предусмотрено стандартом?

Ответ 1

TL; DR: Это заданное поведение в соответствии со стандартом. Вывод аргумента шаблона имеет специальное правило для вывода аргументов шаблона при выборе адреса шаблона функции, что позволяет перенаправить ссылки на работу, как ожидалось. Такого правила для шаблонов функций преобразования нет.

Примечание: это выглядит как область, для которой пока еще никто не написал предложение.Если кто-то пишет предложение для этого, представляется вероятным, что это может быть сделано для работы в будущем.


Из [expr.prim.lambda]:

.... Для общей лямбда без лямбда-захвата тип закрытия имеет шаблон функции преобразования для указателя на функцию. Шаблон функции преобразования имеет тот же самый изобретенный список параметров шаблона, и указатель на функцию имеет те же типы параметров, что и шаблон оператора вызова функции. Тип возврата указателя на функцию должен вести себя так, как если бы это был спецификатор 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 соответствующего параметра- type-list A. Если P и A являются типами функций, которые возникли из вывода при принятии адреса шаблона функции ([temp.deduct.funcaddr]) или при выводе аргументов шаблона из объявления функции ([temp.deduct.decl ]), а P i и A i - параметры списка параметров типа верхнего уровня для P и A, соответственно, P i корректируется, если это ссылка пересылки ([temp.deduct.call]), и A i является ссылка lvalue, и в этом случае тип P i изменяется как тип параметра шаблона (т.е. T&& изменяется на просто T).

добавлен акцент

Это специально вызывает обращение к адресу шаблона функции, поскольку перенаправление ссылок просто работает. Подобной ссылки на шаблоны функций преобразования нет.

Повторяя пример раньше, если мы изменим fn_type на void(*)(T), это та же самая операция, которая описана здесь в стандарте.