我有一个具有可变参数类型的类。在该类中,我有一个方法,它接受这些类型的参数,创建它们的元组并将它们存储在向量中。我想要的是使用完美转发来避免不必要的副本。我通过在方法前添加另一个可变参数模板来解决它,并转发这些新类型而不是旧类型,但我想知道是否有更好的方法。
让我向您展示我的代码示例:
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
}
};
这适用于正确的类型,并且无论如何都不会使用不正确的类型编译,所以我猜它没关系。但我想要的是以某种方式使用方法头中的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
}
};
但这只适用于右值。
那么有没有办法避免使用另一个模板,同时仍然可以完美转发?
答案 0 :(得分:3)
解决问题的最简单方法就是获取一组值,并从中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
{};
现在我们在你的课上使用它:
// 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
内直接扩展测试。
也许我们可以使用std::all_of
和constexpr initializer_list<bool>
在C ++ 11中做同样的事情,但我还没有尝试过。
zip
指的是压缩到相同长度的列表,因此我们按顺序将元素从一个配对到另一个。
这个设计的一个重要缺点是它不支持匿名{}
构造参数,而第一个设计确实如此。还有其他问题,这是完美转发的常见失败。
答案 1 :(得分:2)
正如Yakk在他的回答中所说的那样,有“简单”的方式和“完美的转发”方式来做到这一点。它们并不相同,但我认为他通过固定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
中的左值/右值引用类型。