强制推导模板参数以供参考

时间:2019-06-24 10:22:36

标签: c++ c++11 templates template-deduction

以下代码无法编译,因为编译器推断模板参数为int,而模板参数则要求为int &。在Coliru here上看到它。

#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;
}

在Coliru here上查看。

虽然该解决方案有效,但我发现它很不方便,因为它迫使我提供do_something所有模板类型,这并不是最佳选择,尤其是如果我有一个更复杂的模板带有多个参数的示例,或者如果我想直接将lambda函数add插入为  do_something

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

是否有更方便的方法来强制仅将第三个参数推导为int &

2 个答案:

答案 0 :(得分:6)

尝试

template <class F, class... ArgsType>
void do_something(F f, ArgsType&&... args)
//                            ^^^^ ---------------> perfect forwarding
{
    f(std::forward<ArgsType>(args)...);
}

答案 1 :(得分:3)

了解问题

lambda表达式的第二个参数(sum

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

左值引用

参数包,{p中的ArgsType

template <class F, class... ArgsType>
void do_something(F f, ArgsType... args);
当分别传递变量intint作为参数时,

被推导为参数包is

请记住,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 )。由于无法使用右值(参数)初始化左值引用(参数),因此会导致编译错误。


解决方案

您可以改用forwarding references

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

参数包args的单独参数的类型将始终是引用:

  • 如果将左值作为参数传递,则为左值引用类型。
  • 如果将右值作为参数传递,则为 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));
}

由于reference collapsing适用于static_cast<int& &&>(b),因此表达式的结果为static_cast<int&>(b),它是一个左值。左值引用类型可以用左值初始化。

但是,请注意呼叫:

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

现在不会编译,因为将右值作为第二个参数传递给add。原因类似于您的原始错误。