完美转发和std::tuple
Perfect forwarding and std::tuple
考虑以下代码:
#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;
}
输出将是:
A. First
B. Second
A. Third
A. Fourth
从输出中可以明显看出它没有执行我希望它执行的操作,也就是说我想要 Third 和 Fourth 来完成函数的 B. 版本。
来自 Fourth 调用的 std::forward 是多余的,因为那里不会发生完美转发。为了完美转发我知道:
- 我必须在类型推导上下文中有一个右值引用
- 参数的类型必须是函数的模板类型
我知道它不起作用。但是我没有完全掌握:
为什么使用 std::tuple 更改了上下文,但它无法按预期工作?为什么模板参数不能是类型
对于另一个模板类型?
我该如何(优雅地)修复它?
你的问题是,在 Third 和 Fourth 中,你传递了一个 const std::tuple
,其中 B. 需要一个非常量版本。
当编译器尝试为调用 f
生成代码时,它发现您正在使用 const std::tuple
进行调用,因此推断 Args...
的类型为 const std::tuple
。调用 B. 无效,因为该变量具有与预期不同的常量限定。
要解决这个问题,只需将 g()
return 设为非常量元组即可。
编辑:
为了实现完美转发,您需要一个推断的上下文,正如您在问题中所说的那样。当您在函数参数列表中说 std::tuple<Args...>&&
时,会推导出 Args...
,但不会推导出 std::tuple<Args...>&&
;它可以 仅 通过右值引用。为了解决这个问题,该参数需要采用 T&&
形式,其中推导出 T
。
我们可以使用自定义类型特征来完成此操作:
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>;
is_specialization_of 摘自 here and suggested by this question.
完美转发!
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
考虑以下代码:
#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;
}
输出将是:
A. First
B. Second
A. Third
A. Fourth
从输出中可以明显看出它没有执行我希望它执行的操作,也就是说我想要 Third 和 Fourth 来完成函数的 B. 版本。 来自 Fourth 调用的 std::forward 是多余的,因为那里不会发生完美转发。为了完美转发我知道:
- 我必须在类型推导上下文中有一个右值引用
- 参数的类型必须是函数的模板类型
我知道它不起作用。但是我没有完全掌握:
为什么使用 std::tuple 更改了上下文,但它无法按预期工作?为什么模板参数不能是类型 对于另一个模板类型?
我该如何(优雅地)修复它?
你的问题是,在 Third 和 Fourth 中,你传递了一个 const std::tuple
,其中 B. 需要一个非常量版本。
当编译器尝试为调用 f
生成代码时,它发现您正在使用 const std::tuple
进行调用,因此推断 Args...
的类型为 const std::tuple
。调用 B. 无效,因为该变量具有与预期不同的常量限定。
要解决这个问题,只需将 g()
return 设为非常量元组即可。
编辑:
为了实现完美转发,您需要一个推断的上下文,正如您在问题中所说的那样。当您在函数参数列表中说 std::tuple<Args...>&&
时,会推导出 Args...
,但不会推导出 std::tuple<Args...>&&
;它可以 仅 通过右值引用。为了解决这个问题,该参数需要采用 T&&
形式,其中推导出 T
。
我们可以使用自定义类型特征来完成此操作:
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>;
is_specialization_of 摘自 here and suggested by this question.
完美转发!
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