【问题标题】:Force deduction of template parameter to reference强制扣除模板参数以引用
【发布时间】:2019-06-24 10:22:36
【问题描述】:

以下代码无法编译,因为编译器推断模板参数为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&amp;。一种方法是将模板参数显式传递为

#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 &amp;

【问题讨论】:

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


    【解决方案1】:

    试试

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

    【讨论】:

      【解决方案2】:

      了解问题

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

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

      是一个左值引用

      参数包,ArgsType in

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

      分别在将变量is 作为参数传递时,推导出参数包intint

      请记住 std::forward&lt;ArgsType&gt;(args)...static_cast&lt;ArgsType&amp;&amp;&gt;(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&lt;int&amp;&amp;&gt;(b) 是一个rvalue(更准确地说是一个xvalue)。由于不能用右值(参数)初始化左值引用(参数),因此会导致编译错误。


      解决方案

      您可以改用forwarding references

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

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

      • 如果将左值作为参数传递,则为 左值 引用类型。
      • 如果将右值作为参数传递,则为 rvalue 引用类型。

      这样,对于你的函数调用,参数包ArgsType会被推导出为参数包int&amp;int&amp;,相当于调用下面的函数模板:

      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&lt;int&amp; &amp;&amp;&gt;(b),因此表达式的结果是static_cast&lt;int&amp;&gt;(b),它是一个左值。左值引用类型可以用左值初始化。

      但是,请注意调用:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-06-20
        • 1970-01-01
        • 2019-09-11
        • 1970-01-01
        • 1970-01-01
        • 2021-11-21
        • 1970-01-01
        相关资源
        最近更新 更多