完美转发 class 可变参数
Perfect forwarding class variadic parameters
我有一个带有可变类型参数的 class。在 class 里面,我有一个方法接受这些类型的参数,将它们组成一个元组并将它们存储在一个向量中。我想要的是使用完美转发来避免不必要的复制。我通过在方法前面加上另一个可变参数模板来解决它,我转发这些新类型而不是旧类型,但我想知道是否有更好的方法。
让我向您展示我的代码示例:
template<typename ... Tlist>
class A{
public:
template<typename ... Xlist>
void method(Xlist && ... plist){
// some code
std::vector<std::tuple<Tlist...>> vec;
vec.push_back(std::make_tuple(std::forward<Xlist>(plist)...));
// some other code
}
};
这适用于正确的类型,并且它不会编译不正确的类型,所以我想它没问题。但我想要的是以某种方式在方法 header 中使用 Tlist
类型,像这样:
template<typename ... Tlist>
class A{
public:
void method(Tlist && ... plist){
// some code
std::vector<std::tuple<Tlist...>> vec;
vec.push_back(std::make_tuple(std::forward<Tlist>(plist)...));
// some other code
}
};
但这只适用于右值。
那么有没有办法避免使用另一个模板,同时仍然可以完美转发?
解决这个问题最简单的方法就是简单地取一包值,然后从中 move
:
template<class...Ts>
struct A{
void method(Ts...ts){
// some code
std::vector<std::tuple<Ts...>> vec;
vec.emplace_back(std::forward_as_tuple(std::move(ts)...));
// some other code
}
};
如果 Ts
包含引用,则上面的代码表现不佳,但您的原始代码也没有。它还会强制使用冗余 move
,这对于某些类型来说是昂贵的。最后,如果您没有支持 vec
,它会强制您的类型 可移动 -- 下面的解决方案没有。
这是迄今为止对您的问题最简单的解决方案,但实际上并不完美。
这是一个更复杂的解决方案。我们从一些元编程开始。
types
是一组类型:
template<class...>struct types{using type=types;};
conditional_t
是一个 C++14 别名模板,使其他代码更简洁:
// not needed in C++14, use `std::conditional_t`
template<bool b, class lhs, class rhs>
using conditional_t = typename std::conditional<b,lhs,rhs>::type;
zip_test
采用一个测试模板和两个类型列表。它依次测试 lhs
的每个元素与 rhs
的相应元素。如果全部通过,则为true_type
,否则为false_type
。如果列表长度不匹配,则无法编译:
template<template<class...>class test, class lhs, class rhs>
struct zip_test; // fail to compile, instead of returning false
template<
template<class...>class test,
class L0, class...lhs,
class R0, class...rhs
>
struct zip_test<test, types<L0,lhs...>, types<R0,rhs...>> :
conditional_t<
test<L0,R0>{},
zip_test<test, types<lhs...>, types<rhs...>>,
std::false_type
>
{};
template<template<class...>class test>
struct zip_test<test, types<>, types<>> :
std::true_type
{};
现在我们在您的 class 上使用它:
// also not needed in C++14:
template<bool b, class T=void>
using enable_if_t=typename std::enable_if<b,T>::type;
template<class T>
using decay_t=typename std::decay<T>::type;
template<class...Ts>
struct A{
template<class...Xs>
enable_if_t<zip_test<
std::is_same,
types< decay_t<Xs>... >,
types< Ts... >
>{}> method(Xs&&... plist){
// some code
std::vector<std::tuple<Tlist...>> vec;
vec.emplace_back(
std::forward_as_tuple(std::forward<Xlist>(plist)...)
);
// some other code
}
};
将 Xs
限制为与 Ts
完全相同。现在我们可能想要一些稍微不同的东西:
template<class...Xs>
enable_if_t<zip_test<
std::is_convertible,
types< Xs&&... >,
types< Ts... >
>{}> method(Xs&&... plist){
我们在这里测试传入的参数是否可以转换为存储的数据。
我又改了forward_as_tuple
代替了make_tuple
,emplace
代替了push
,这两个都是为了让完美转发一路走下去下来。
对于上述代码中的任何拼写错误,我们深表歉意。
请注意,在 C++1z 中,我们可以不使用 zip_test
,而是使用折叠表达式直接扩展 enable_if
中的测试。
也许我们可以在 C++11 中使用 std::all_of
和 constexpr initializer_list<bool>
做同样的事情,但我还没有尝试过。
zip
在此上下文中指的是压缩到相同长度的列表,因此我们按照从一个到另一个的顺序对元素进行配对。
此设计的一个显着缺点是它不支持匿名 {}
参数构造,而第一个设计支持。还有其他问题,都是完美转发的常见故障。
正如 Yakk 在他的回答中所说,有 "easy" 方式和 "perfect forwarding" 方式来做到这一点。它们并不相同,但我认为他通过关注 forward_as_tuple
和 tuple
的转换构造函数使情况过于复杂。即 (DEMO):
template<typename ... Ts>
class A {
public:
// "Simple" method. Does not perfect forward.
// Caller's expressions are copied/moved/converted at the callsite
// into Ts, and then moved (for non-reference types) or copied
// (reference types) into the vector.
void simple_method(Ts... ts) {
// some code
std::vector<std::tuple<Ts...>> vec;
vec.emplace_back(std::forward<Ts>(ts)...);
// some other code
}
// "Perfect forwarding" method.
// Caller's expressions are perfectly forwarded into the vector via
// emplace.
template <typename...Us>
void perfect_forwarding_method(Us&&...us) {
// some code
std::vector<std::tuple<Ts...>> vec;
vec.emplace_back(std::forward<Us>(us)...);
// some other code
}
// Constraint alias.
template <typename...Us>
using Constructible = typename std::enable_if<
std::is_constructible<std::tuple<Ts...>, Us...>::value
>::type;
// "Constrained Perfect forwarding" method.
// Caller's expressions are perfectly forwarded into the vector via
// emplace. Substitution failure if tuple<Ts...> cannot be constructed
// from std::forward<Us>(us)...
template <typename...Us, typename = Constructible<Us...>>
void constrained_perfect_forwarding_method(Us&&...us) {
// some code
std::vector<std::tuple<Ts...>> vec;
vec.emplace_back(std::forward<Us>(us)...);
// some other code
}
};
所有三种方法都能正确处理 Ts
中的 lvalue/rvalue 引用类型。
我有一个带有可变类型参数的 class。在 class 里面,我有一个方法接受这些类型的参数,将它们组成一个元组并将它们存储在一个向量中。我想要的是使用完美转发来避免不必要的复制。我通过在方法前面加上另一个可变参数模板来解决它,我转发这些新类型而不是旧类型,但我想知道是否有更好的方法。
让我向您展示我的代码示例:
template<typename ... Tlist>
class A{
public:
template<typename ... Xlist>
void method(Xlist && ... plist){
// some code
std::vector<std::tuple<Tlist...>> vec;
vec.push_back(std::make_tuple(std::forward<Xlist>(plist)...));
// some other code
}
};
这适用于正确的类型,并且它不会编译不正确的类型,所以我想它没问题。但我想要的是以某种方式在方法 header 中使用 Tlist
类型,像这样:
template<typename ... Tlist>
class A{
public:
void method(Tlist && ... plist){
// some code
std::vector<std::tuple<Tlist...>> vec;
vec.push_back(std::make_tuple(std::forward<Tlist>(plist)...));
// some other code
}
};
但这只适用于右值。
那么有没有办法避免使用另一个模板,同时仍然可以完美转发?
解决这个问题最简单的方法就是简单地取一包值,然后从中 move
:
template<class...Ts>
struct A{
void method(Ts...ts){
// some code
std::vector<std::tuple<Ts...>> vec;
vec.emplace_back(std::forward_as_tuple(std::move(ts)...));
// some other code
}
};
如果 Ts
包含引用,则上面的代码表现不佳,但您的原始代码也没有。它还会强制使用冗余 move
,这对于某些类型来说是昂贵的。最后,如果您没有支持 vec
,它会强制您的类型 可移动 -- 下面的解决方案没有。
这是迄今为止对您的问题最简单的解决方案,但实际上并不完美。
这是一个更复杂的解决方案。我们从一些元编程开始。
types
是一组类型:
template<class...>struct types{using type=types;};
conditional_t
是一个 C++14 别名模板,使其他代码更简洁:
// not needed in C++14, use `std::conditional_t`
template<bool b, class lhs, class rhs>
using conditional_t = typename std::conditional<b,lhs,rhs>::type;
zip_test
采用一个测试模板和两个类型列表。它依次测试 lhs
的每个元素与 rhs
的相应元素。如果全部通过,则为true_type
,否则为false_type
。如果列表长度不匹配,则无法编译:
template<template<class...>class test, class lhs, class rhs>
struct zip_test; // fail to compile, instead of returning false
template<
template<class...>class test,
class L0, class...lhs,
class R0, class...rhs
>
struct zip_test<test, types<L0,lhs...>, types<R0,rhs...>> :
conditional_t<
test<L0,R0>{},
zip_test<test, types<lhs...>, types<rhs...>>,
std::false_type
>
{};
template<template<class...>class test>
struct zip_test<test, types<>, types<>> :
std::true_type
{};
现在我们在您的 class 上使用它:
// also not needed in C++14:
template<bool b, class T=void>
using enable_if_t=typename std::enable_if<b,T>::type;
template<class T>
using decay_t=typename std::decay<T>::type;
template<class...Ts>
struct A{
template<class...Xs>
enable_if_t<zip_test<
std::is_same,
types< decay_t<Xs>... >,
types< Ts... >
>{}> method(Xs&&... plist){
// some code
std::vector<std::tuple<Tlist...>> vec;
vec.emplace_back(
std::forward_as_tuple(std::forward<Xlist>(plist)...)
);
// some other code
}
};
将 Xs
限制为与 Ts
完全相同。现在我们可能想要一些稍微不同的东西:
template<class...Xs>
enable_if_t<zip_test<
std::is_convertible,
types< Xs&&... >,
types< Ts... >
>{}> method(Xs&&... plist){
我们在这里测试传入的参数是否可以转换为存储的数据。
我又改了forward_as_tuple
代替了make_tuple
,emplace
代替了push
,这两个都是为了让完美转发一路走下去下来。
对于上述代码中的任何拼写错误,我们深表歉意。
请注意,在 C++1z 中,我们可以不使用 zip_test
,而是使用折叠表达式直接扩展 enable_if
中的测试。
也许我们可以在 C++11 中使用 std::all_of
和 constexpr initializer_list<bool>
做同样的事情,但我还没有尝试过。
zip
在此上下文中指的是压缩到相同长度的列表,因此我们按照从一个到另一个的顺序对元素进行配对。
此设计的一个显着缺点是它不支持匿名 {}
参数构造,而第一个设计支持。还有其他问题,都是完美转发的常见故障。
正如 Yakk 在他的回答中所说,有 "easy" 方式和 "perfect forwarding" 方式来做到这一点。它们并不相同,但我认为他通过关注 forward_as_tuple
和 tuple
的转换构造函数使情况过于复杂。即 (DEMO):
template<typename ... Ts>
class A {
public:
// "Simple" method. Does not perfect forward.
// Caller's expressions are copied/moved/converted at the callsite
// into Ts, and then moved (for non-reference types) or copied
// (reference types) into the vector.
void simple_method(Ts... ts) {
// some code
std::vector<std::tuple<Ts...>> vec;
vec.emplace_back(std::forward<Ts>(ts)...);
// some other code
}
// "Perfect forwarding" method.
// Caller's expressions are perfectly forwarded into the vector via
// emplace.
template <typename...Us>
void perfect_forwarding_method(Us&&...us) {
// some code
std::vector<std::tuple<Ts...>> vec;
vec.emplace_back(std::forward<Us>(us)...);
// some other code
}
// Constraint alias.
template <typename...Us>
using Constructible = typename std::enable_if<
std::is_constructible<std::tuple<Ts...>, Us...>::value
>::type;
// "Constrained Perfect forwarding" method.
// Caller's expressions are perfectly forwarded into the vector via
// emplace. Substitution failure if tuple<Ts...> cannot be constructed
// from std::forward<Us>(us)...
template <typename...Us, typename = Constructible<Us...>>
void constrained_perfect_forwarding_method(Us&&...us) {
// some code
std::vector<std::tuple<Ts...>> vec;
vec.emplace_back(std::forward<Us>(us)...);
// some other code
}
};
所有三种方法都能正确处理 Ts
中的 lvalue/rvalue 引用类型。