强制扣除模板参数要参考

Force deduction of template parameter to reference

本文关键字:参数 参考      更新时间:2023-10-16

以下代码不编译,因为编译器推论一个模板参数为 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;
}

在这里在Coliru上看到它。

当解决方案起作用时,我觉得不便,因为它迫使我提供 ack_5的所有模板类型,这不是最佳的,特别是如果我有一个更复杂的示例,其中有几个参数,或者,如果我想直接插入lambda函数add作为参数 do_something

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

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

尝试

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

了解问题

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

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

lvalue参考

参数包,

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

分别将变量is作为参数推荐到参数 intint

请记住, 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 (更精确(。由于您无法用rvalue(参数(初始化lvalue参考(参数(,因此会导致汇编误差。


解决方案

您可以使用转发引用:

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

参数包args的单独参数的类型始终是参考:

  • lvalue 参考类型如果通过lvalue作为参数。
  • rvalue 参考类型如果将rvalue作为参数传递。

以这种方式,对于您的函数调用,参数pack ArgsType将推荐为参数pack 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)中导致 CC_24,这是一个LVALUE。可以使用LVALUE初始化LVALUE参考类型。

但是,请注意:

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

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