Принудительно удерживать параметр шаблона для ссылки - PullRequest
2 голосов
/ 24 июня 2019

Следующий код не компилируется, потому что компилятор выводит параметр шаблона равным int, в то время как он должен быть int &. Смотрите это на Coliru здесь .

#include <iostream>
#include <utility>

template <class F, class... ArgsType>
void do_something(F f, ArgsType... args)
{
    f(std::forward<ArgsType>(args)...);
}

int main()
{
    int s = 2;
    int i = 1;
    auto add = [](const int a, int& sum) { sum += a; };
    do_something(add, i, s);
    std::cout << s << std::endl;

    return 0;
}

Ошибка:

main.cpp: In instantiation of 'void do_something(F, ArgsType ...) [with F = main()::<lambda(int, int&)>; ArgsType = {int, int}]':

main.cpp:15:27:   required from here

main.cpp:7:6: error: no match for call to '(main()::<lambda(int, int&)>) (int, int)'

     f(std::forward<ArgsType>(args)...);

     ~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

main.cpp:7:6: note: candidate: 'void (*)(int, int&)' <conversion>

main.cpp:7:6: note:   conversion of argument 3 would be ill-formed:

main.cpp:7:6: error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'

main.cpp:14:40: note: candidate: 'main()::<lambda(int, int&)>' <near match>

     auto add = [](const int a, int& sum) { sum += a; };

                                        ^

main.cpp:14:40: note:   conversion of argument 2 would be ill-formed:

main.cpp:7:6: error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'

     f(std::forward<ArgsType>(args)...);

     ~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

В идеале третий аргумент do_something должен быть выведен как int&. Один из способов сделать это - явно передать параметры шаблона как

#include <iostream>
#include <utility>

template <class F, class... ArgsType>
void do_something(F f, ArgsType... args)
{
    f(std::forward<ArgsType>(args)...);
}

int main()
{
    int s = 2;
    int i = 1;
    auto add = [](const int a, int& sum) { sum += a; };
    do_something<decltype(add), const int, int&>(add, i, s);
    std::cout << s << std::endl;

    return 0;
}

Смотрите это на Колиру здесь .

Хотя решение работает, я нахожу его неудобным, потому что оно заставляет меня предоставлять все типы шаблонов do_something, что не оптимально, особенно если, скажем, у меня есть более сложный пример с несколькими параметры, или если я хотел бы вставить лямбда-функцию add непосредственно в качестве параметра do_something:

do_something([](const int a, int& sum) { sum += a; }, i, s);

Есть ли более удобный способ заставить выводить только третий параметр как int &?

Ответы [ 2 ]

6 голосов
/ 24 июня 2019

Попробуйте

template <class F, class... ArgsType>
void do_something(F f, ArgsType&&... args)
//                            ^^^^ ---------------> perfect forwarding
{
    f(std::forward<ArgsType>(args)...);
}
3 голосов
/ 24 июня 2019

Понимание проблемы

второй параметр (sum) вашего лямбда-выражения

auto add = [](const int a, int& sum) { sum += a; };

является ссылкой lvalue .

Пакет параметров, ArgsType в

template <class F, class... ArgsType>
void do_something(F f, ArgsType... args);

выводится в пакет аргументов int, int при передаче переменных i и s в качестве аргументов соответственно.

Учитывая, что std::forward<ArgsType>(args)... совпадает с static_cast<ArgsType&&>(args)... (т. Е. Он просто приводится к типу rvalue ), вызов do_something() будет эквивалентен вызову следующего шаблон функции:

template <class F>
void do_something(F f, int a, int b)
{
    f(static_cast<int&&>(a), static_cast<int&&>(b));
}

Выражение static_cast<int&&>(b) представляет собой rvalue (точнее, xvalue ). Поскольку вы не можете инициализировать ссылку на lvalue (параметр) значением rvalue (аргумент), это приведет к ошибке компиляции.


Решение

Вы можете использовать переадресацию ссылок вместо:

template <class F, class... ArgsType>
void do_something(F f, ArgsType&&... args);

Тип отдельных аргументов пакета аргументов args всегда будет ссылкой:

  • Тип ссылки lvalue , если в качестве аргумента передано lvalue.
  • rvalue ссылочный тип, если в качестве аргумента передано rvalue.

Таким образом, для вашего вызова функции пакет параметров ArgsType будет выведен в пакет аргументов int&, int&, который будет эквивалентен вызову следующего шаблона функции:

template <class F>
void do_something(F f, int& a, int& b)
{
    f(static_cast<int& &&>(a), static_cast<int& &&>(b));
}

Поскольку свертывание ссылки применяется к static_cast<int& &&>(b), выражение приводит к static_cast<int&>(b), что является lvalue. Тип ссылки lvalue может быть инициализирован значением lvalue.

Однако учтите, что звонок:

do_something(add, i, 7);
                     ^
                     |
                     --- rvalue

не будет компилироваться сейчас, поскольку rvalue передается в add в качестве второго аргумента. Рассуждение похоже на исходную ошибку.

...