Perfect Forwarding завершается ошибкой, когда (распаденный) тип выводится из аргумента шаблона класса функтора. Почему? - PullRequest
1 голос
/ 18 апреля 2020

В приведенном ниже примере

#include <iostream>
#include <utility>
//okay:
//
template<typename T, typename F>
decltype(auto) runner(T&& t, F f)
{
    return f(std::forward<T>(t));
}

//okay:
//
struct runner_t_f {
    template<typename T>
    void operator()(T&& t)
        {
            std::cout<<"template op(): "<<t<<'\n';
        }
};

template<typename T>
struct runner_t {
    void operator()(T&& t) //error: cannot bind 'T' lvalue to 'T&&'
        {
            std::cout<<"template functor: "<<t<<'\n';
        }
};

int main(void)
{
    int j{13};

    auto res = runner(j, [](int const& x) -> double { //okay
            return static_cast<double>(x*x);
        });
    std::cout<<res<<'\n';

    runner_t_f rtf{};
    rtf(j);//okay

    runner_t<int> rt{};
    rt(j);//not okay...why?
    //error: cannot bind ‘int’ lvalue to ‘int&&’

    return 0;
}

Я пытаюсь создать эти runner батутные функции, используя идеальную переадресацию. Вся компиляция (и выполнение) кроме runner_t, которая завершается с ошибкой, указанной в приведенном выше коде. Почему?

И как я могу смоделировать то, что пытается сделать компилятор, чтобы я мог понять, что не так? Заранее спасибо.

Ответы [ 3 ]

3 голосов
/ 18 апреля 2020

Пересылка ссылок работает из-за свертывания ссылок и правил вывода типов шаблонов.

В простой функции:

template <typename T>
void foo(T&& t) {}
  • Когда вызывается как foo(42), T является выводится равным int, и поэтому T&& становится int&&: rvalue-reference-to- int.
  • При вызове int i = 42; foo(i), T определяется как int& и T&& становится int& &&. Поскольку у вас не может быть ссылки на ссылку, правила свертывания ссылок включаются и оставляют вас с int&: lvalue-reference-to- int.

Что все это означает что в этом:

template<typename T>
struct runner_t {
    void operator()(T&& t) //error: cannot bind 'T' lvalue to 'T&&'
        {
            std::cout<<"template functor: "<<t<<'\n';
        }
};
  • Когда вы указываете runner_t<int>, T&& становится int&&, а rvalue-ссылка не может привязываться к j, так как это lvalue.

  • Если вместо этого вы указали тип rt, равный runner_t<int&>, тогда тип t будет int& &&, что приведет к падению до int&. Это может связать с j.

2 голосов
/ 18 апреля 2020

Чтобы смоделировать, что делает компилятор, вам нужно что-то вроде этого:

    runner_t<decltype((j))> rt{};
    rt(j);

Нам нужно передать тип выражения j (что мы делаем с дополнительными скобками). Это lvalue, так что вы получаете T = int& и T&& = int&, которые мы можем передать j как.

Это раньше не работало, потому что с T = int, T&& = int&&, который может только быть привязанным к rvalues, поэтому вам придется сделать что-то вроде rt(int{j}) (кстати, decltype((int{j})) = int, поэтому runner_t<decltype((int{j}))>{}(int{j}) все равно будет работать)

0 голосов
/ 19 апреля 2020

Ах, случай четко задокументирован в «Шаблонах C ++ - Полное руководство», 2-е изд., Josuttis et. al., раздел 15.6.4, стр. 283:

"Результаты специального правила вывода для ссылок на rvalue очень полезны для идеальной пересылки. Однако они могут стать неожиданностью [...]. ..это поведение вычета применяется только тогда, когда параметр функции [...] является частью шаблона функции, и указанный параметр шаблона объявлен этим шаблоном функции . Следовательно, это правило вывода делает не применяется в любой из следующих ситуаций: "

template<typename T>
class X
{
public:
   //...
   X(T&& ); //this constructor is not a function template
   //...
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...