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。您不使用嵌套名称说明符,因此不会导致替换失败,2T
可以被推断为左值参考,因此你的特质无法测试它。基于函数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