C++ 完美转发和std::tuple

C++ 完美转发和std::tuple,c++,tuples,perfect-forwarding,C++,Tuples,Perfect Forwarding,考虑以下代码: #include <iostream> #include <tuple> #include <utility> // A. template <typename... Args> void f (const char* msg, Args&&... args) { std::cout << "A. " << msg << "\n"; } // B. template &

考虑以下代码:

#include <iostream>
#include <tuple>
#include <utility>

// A.
template <typename... Args>
void f (const char* msg, Args&&... args)
{
    std::cout << "A. " << msg << "\n";
}

// B.
template <typename... Args>
void f (const char* msg, std::tuple<Args...>&& t)
{
    std::cout << "B. " << msg << "\n";
}

struct boo
{
    const std::tuple<int, int, long> g () const
    {
        return std::make_tuple(2, 4, 12345);
    }
};

int main ()
{
    f("First", 2, 5, 12345);
    f("Second", std::make_tuple(2, 5, 12345));

    boo the_boo;
    f("Third", the_boo.g());
    f("Fourth", std::forward<decltype(std::declval<boo>().g())>(the_boo.g()));

    return 0;
}
从输出来看,很明显,它没有做我希望它做的事情,也就是说,我希望第三个和第四个完成函数的B.版本。 来自第四个呼叫的std::转发是多余的,因为完美转发不会发生在那里。为了实现完美的转发,我知道:

  • 我必须在类型推断上下文中有一个右值引用
  • 参数的类型必须是函数的模板类型
我知道它不起作用。但我并不完全理解:

  • 为什么使用std::tuple来更改上下文,使其无法按预期工作?为什么模板参数不能为该类型 对于另一个模板类型

  • 我如何(优雅地)修复它


    • 您的问题是,在第三个和第四个字段中,您传递的是一个
      常量std::tuple
      ,其中B.需要一个非常量版本

      当编译器试图为调用
      f
      生成代码时,它会看到您正在使用
      const std::tuple
      进行调用,从而将
      Args…
      的类型推断为
      const std::tuple
      。调用B.无效,因为变量的常量限定条件与预期的不同

      要解决这个问题,只需使
      g()
      返回一个非常量元组


      编辑:

      为了实现完美的转发,您需要一个推断的上下文,正如您在问题中所说的。当您在函数参数列表中说
      std::tuple&
      时,
      Args…
      被推导,但
      std::tuple&
      不是;它只能通过右值引用进行访问。为了解决这个问题,这个参数需要采用
      T&
      的形式,其中
      T
      是推导出来的

      我们可以使用自定义类型特征来实现这一点:

      template <typename T>
      struct is_tuple : std::false_type {};
      
      template <typename... Args>
      struct is_tuple <std::tuple<Args...>> : std::true_type {};
      

      B
      更大的问题是非常量值引用无法绑定到常量值。我明白了。编译器告诉我同样的事情(稍加修改代码)。我只是不知道如何修复它。不让
      g
      返回常量元组?(为什么它会返回一个?@T.C.阅读C++11“有效的C++”系列之前的内容。@celavek,不要返回常量值,这是一个可以追溯到下一个十年并阻止移动语义的坏习惯。我不确定Meyers,但Sutter不再建议返回常量值。总之,答案是有其优点的,因为它使我认识到并阅读了更多关于C++ 11中最好不要用const值返回的事实(我习惯于按照有效的C++建议那样做),但是这里有一个细微的细微差别:B不能接受“const STD::元组”因为它不能从它移动,因为移动语义涉及到它的签名和不完美的转发。所以我的问题仍然没有答案。我可以按照你的建议去做(事实上我已经试过了),但这并不能解决我在完美转发方面的问题。我明白你的意思。该函数的实际语义是什么?用一个单独的元组来接受元组可能会更容易。你所说的实际语义是什么意思?我拥有的真正的代码案例就是——除了函数做一些事情,而不是将一些东西打印到标准输出。你是说一个名字不同的函数?我仍然需要检测到我在某处接收到一个元组的事实。为什么我没有想到呢?:)。我冒昧地用另一种解决办法补充了答案。如果由于以下众多原因出错,请感谢
      typename=typename std::enable\u:1。您不使用嵌套名称说明符,因此不会导致替换失败,2
      T
      可以被推断为左值参考,因此你的特质无法测试它。基于函数decltype的转发也没有什么意义
      template <typename T>
      struct is_tuple : std::false_type {};
      
      template <typename... Args>
      struct is_tuple <std::tuple<Args...>> : std::true_type {};
      
      // B.
      template <typename T, typename = typename std::enable_if<
                                is_tuple<typename std::decay<T>::type>::value
                                >::type>
      void f (const char* msg, T&& t)
      {
          std::cout << "B. " << msg << "\n";
          std::cout << "B. is lval == " << std::is_lvalue_reference<T>() << "\n";
      }
      
      //! Tests if T is a specialization of Template
      template <typename T, template <typename...> class Template>
      struct is_specialization_of : std::false_type {};
      
      template <template <typename...> class Template, typename... Args>
      struct is_specialization_of<Template<Args...>, Template> : std::true_type {};
      
      template <typename T>
      using is_tuple = is_specialization_of<T, std::tuple>;
      
      int main ()
      {
          f("First", 2, 5, 12345);
          f("Second", std::make_tuple(2, 5, 12345));
      
          boo the_boo;
          f("Third", the_boo.g());
          f("Fourth", std::forward<decltype(std::declval<boo>().g())>(the_boo.g()));
      
          auto the_g = the_boo.g();
          f("Fifth", the_g);
      
          return 0;
      }
      
      A. First
      B. Second
      B. is lval == 0
      B. Third
      B. is lval == 0
      B. Fourth
      B. is lval == 0
      B. Fifth
      B. is lval == 1