模板参数包访问第 N 个类型和第 N 个元素
template parameter packs access Nth type and Nth element
以下论文是我找到的第一个模板参数包建议。
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1603.pdf
在第 16 页,它讨论了引入两个新的运算符 [] 和<>,用于访问参数包元素和参数包类型。
The suggested syntax for such an operator involves two new operators: .[] to access values and .<> to access types. For instance:
template<int N, typename Tuple> struct tuple_element;
template<int N, ... Elements>
struct tuple_element<tuple<Elements...> >
{
typedef Elements.<N> type;
};
template<int N, ... Elements>
Elements.<N>& get(tuple<Elements...>& t)
{ return t.[N]; }
template<int N, ... Elements>
const Elements.<N>& get(const tuple<Elements...>& t)
{ return t.[N]; }
那么这些运营商在哪里?如果没有,它们的替代品是什么?
其他人已经回答说可以通过std::tuple
来完成。如果你想访问第 N 种类型的参数包,你可能会发现以下元函数很方便:
template<int N, typename... Ts> using NthTypeOf =
typename std::tuple_element<N, std::tuple<Ts...>>::type;
用法:
using ThirdType = NthTypeOf<2, Ts...>;
C++11 没有相应的运算符,这就是它们被提出的原因。使用 C++11,您需要自己提取相应的信息,或者使用已经执行必要操作的类。最简单的方法可能是只使用已经实现相应逻辑的std::tuple<T...>
。
如果你想知道std::tuple<T...>
目前是如何实现这些操作的:它基本上是一个使用相当糟糕的函数式编程符号的函数式编程练习。一旦您知道如何获取序列的第n
类型,使用从索引和类型参数化的基类的继承来获取第 n
-th 元素是相当简单的。实现类似tuple_element<N, T...>
的东西可能看起来像这样:
template <int N, typename... T>
struct tuple_element;
template <typename T0, typename... T>
struct tuple_element<0, T0, T...> {
typedef T0 type;
};
template <int N, typename T0, typename... T>
struct tuple_element<N, T0, T...> {
typedef typename tuple_element<N-1, T...>::type type;
};
在实现类似 std::tuple<T...>
的东西时,实际更具挑战性的一点是变出一个索引列表,这样你就得到了一个类型和整数的并行列表,然后可以对其进行扩展,例如,对于基类列表,使用类似的东西(内部细节的外观会有所不同,但为类型及其索引提供并行参数包的基本思想将以某种方式存在(:
template <typename... T, int... I>
class tuple_base<tuple_types<T...>, tuple_indices<I...>>:
public tuple_field<T, I>... {
};
访问第 N 个元素?
使用std::forward_as_tuple
:
template <int I, class... Ts>
decltype(auto) get(Ts&&... ts) {
return std::get<I>(std::forward_as_tuple(ts...));
}
用法示例:
template<class...Ts>
void foo(Ts&&...ts){
auto& first = get<0>(ts...);
auto second = get<1>(ts...);
first = 'H';
second = 'E';
(std::cout << ... << ts);
}
foo('h','e','l','l','o');
// prints "Hello"
这个答案是对埃米尔·科米尔的回答的补充,该答案只给出了第n种类型。
要从包中获取第 N 个元素,您可以编写:
选项 1
使用 tuple_element 获取第 N 个元素的返回类型:
template<size_t index, typename T, typename... Ts>
inline constexpr typename enable_if<index==0, T>::type
get(T&& t, Ts&&... ts) {
return t;
}
template<size_t index, typename T, typename... Ts>
inline constexpr typename enable_if<(index > 0) && index <= sizeof...(Ts),
typename tuple_element<index, tuple<T, Ts...>>::type>::type
get(T&& t, Ts&&... ts) {
return get<index-1>(std::forward<Ts>(ts)...);
}
// below is optional - just for getting a more readable compilation error
// in case calling get with a bad index
inline template<long long index, typename... Ts>
constexpr bool index_ok() {
return index >= 0 && index < sizeof...(Ts);
}
template<long long index, typename T, typename... Ts>
inline constexpr
typename enable_if<!index_ok<index, T, Ts...>(), T>::type
get(T&& t, Ts&&... ts) {
static_assert(index_ok<index, T, Ts...>(),
"bad index in call to get, smaller than zero or above pack size");
return t;
}
选项 2
不使用元组,依赖于自动返回类型,特别是C++14 decltype(auto(以及使用enable_if作为模板参数而不是返回类型:
template<size_t index, typename T, typename... Ts,
typename enable_if<index==0>::type* = nullptr>
inline constexpr decltype(auto) get(T&& t, Ts&&... ts) {
return std::forward<T>(t);
}
template<size_t index, typename T, typename... Ts,
typename enable_if<(index > 0 && index <= sizeof...(Ts))>::type* = nullptr>
inline constexpr decltype(auto) get(T&& t, Ts&&... ts) {
return get<index-1>(std::forward<Ts>(ts)...);
}
template<long long index, typename... Ts>
inline constexpr bool index_ok() {
return index >= 0 && index < (long long)sizeof...(Ts);
}
// block (compilation error) the call to get with bad index,
// providing a readable compilation error
template<long long index, typename T, typename... Ts,
typename enable_if<(!index_ok<index, T, Ts...>())>::type* = nullptr>
inline constexpr decltype(auto) get(T&& t, Ts&&... ts) {
static_assert(index_ok<index, T, Ts...>(),
"bad index in call to get, smaller than zero or above pack size");
return std::forward<T>(t); // need to return something...
// we hope to fail on the static_assert above
}
使用示例:
template<size_t index, typename... Ts>
void resetElementN(Ts&&... ts) {
get<index>(std::forward<Ts>(ts)...) = {}; // assuming element N has an empty ctor
}
int main() {
int i = 0;
string s = "hello";
get<0>(i,2,"hello","hello"s, 'a') += get<0>(2);
get<1>(1,i,"hello",4) += get<1>(1, 2);
get<3>(1,2,"hello",i) += get<2>(0, 1, 2);
get<2>(1,2,s,4) = get<2>(0, 1, "hi");
cout << i << ' ' << s << endl;
resetElementN<1>(0, i, 2);
resetElementN<0>(s, 1, 2);
cout << i << ' ' << s << endl;
// not ok - and do not compile
// get<0>(1,i,"hello","hello"s) = 5;
// get<1>(1,i*2,"hello") = 5;
// get<2>(1,i*2,"hello")[4] = '!';
// resetElementN<1>(s, 1, 2);
// ok
const int j = 2;
cout << get<0>(j,i,3,4) << endl;
// not ok - and do not compile
// get<0>(j,i,3,4) = 5;
// not ok - and do not compile
// with a readable compilation error
// cout << get<-1>("one", 2, '3') << endl;
// cout << get<3>("one", 2, '3') << endl;
}
法典
选项 1:http://coliru.stacked-crooked.com/a/60ad3d860aa94453
选项 2:http://coliru.stacked-crooked.com/a/09f6e8e155612f8b
我们可以实现一个简单的函数来直接获取第 n 个参数,而无需任何递归调用,而是在编译时进行许多纯类型操作。我们先来看看密钥代码:
template<class...Ts>
struct GetImp {
template<class T, class...Us>
static decltype(auto) impl(Ts&&..., T&& obj, Us&&...) {
return std::forward<T>(obj);
}
};
template<size_t n, class...Ts>
decltype(auto) get(Ts&&...args) {
static_assert(n<sizeof...(args), "index over range");
return Transform<GetImp, Before_s<n, Seq<Ts...>> >
::impl(std::forward<Ts>(args)...);
}
转换是什么意思?
例如,如果我们有一个类型 T
是 std::tuple<int,double,float>
,那么Transform<GetImp,T>
GetImp<int,double,float>
.请注意,我定义了另一个空结构"Seq"而不是std::tuple
同样的事情,编译时间更短。(事实上它们都可以很快编译,但我想空结构会更有效(所以Before_s<n,Seq<Ts...>>
生成一个Seq<?>
然后我们将其转换为 GetImp,这样我们就可以知道 [0]~[n-1] 参数是什么类型,然后删除它们以直接索引第 n 个参数。例如,Before_s<3,Seq<T0,T1,T2,T3,T4...>>
是Seq<T0,T1,T2>
, Before_s<2,Seq<T0,T1,T2,T3,T4...>>
是Seq<T0,T1>
等。我们使用Before_s来处理我们的 Seq 类型,以减少编译时间,当我们使用一个元函数实现另一个元函数以减少编译时间。
实现
#define OMIT_T(...) typename __VA_ARGS__::type
template<class...Args>
struct Seq { };
template< template<class...> class Dst >
struct TransformImp{
template< template<class...>class Src, class...Args >
static Dst<Args...> from(Src<Args...>&&);
};
template< template<class...> class Dst, class T>
using Transform = decltype(TransformImp<Dst>::from(std::declval<T>()));
template<class T>
using Seqfy = Transform<Seq, T>;
template<class...>struct MergeImp;
template<class...Ts, class...Others>
struct MergeImp<Seq<Ts...>, Seq<Others...>>
{
using type = Seq<Ts..., Others...>;
};
template<class first, class second>
using Merge = OMIT_T(MergeImp<Seqfy<first>, Seqfy<second> >);
template<class T, class U>
using Merge_s = OMIT_T(MergeImp<T, U>);
template<size_t, class...>struct BeforeImp;
template<size_t n, class T, class...Ts>
struct BeforeImp<n, Seq<T, Ts...>> {
static_assert(n <= sizeof...(Ts)+1, "index over range");
using type = Merge_s<Seq<T>, OMIT_T(BeforeImp<n - 1, Seq<Ts...>>)>;
};
template<class T, class...Ts>
struct BeforeImp<1, Seq<T, Ts...>> {
using type = Seq<T>;
};
template<class T, class...Ts>
struct BeforeImp<0, Seq<T, Ts...>> {
using type = Seq<>;
};
template<size_t n>
struct BeforeImp<n, Seq<>> {
using type = Seq<>;
};
template<size_t n, class T>
using Before = OMIT_T(BeforeImp<n, Seqfy<T>>);
template<size_t n, class T>
using Before_s = OMIT_T(BeforeImp<n, T>);
已编辑:高级实现
我们不需要在第 n 个类型之前使用 Before_s 来计算 n-1 个类型,相反,我们可以忽略它们:
struct EatParam{
constexpr EatParam(...)noexcept{}
};
template<size_t n>
struct GenSeqImp {
using type = Merge_s<OMIT_T(GenSeqImp<n / 2>), OMIT_T(GenSeqImp<n - n / 2>)>;
};
template<>
struct GenSeqImp<0> {
using type = Seq<>;
};
template<>
struct GenSeqImp<1> {
using type = Seq<EatParam>;
};
template<size_t n>
using GenSeq = OMIT_T(GenSeqImp<n>);
template<class...Ts>
struct GetImp {
template<class T>
static constexpr decltype(auto) impl(Ts&&..., T&& obj, ...)noexcept {
return std::forward<T>(obj);
}
};
template<size_t n, class...Ts>
constexpr decltype(auto) get(Ts&&...args)noexcept {
static_assert(n<sizeof...(args), "index over range.");
//return Transform<GetImp, Before_s<n, Seq<Ts...>> >
return Transform<GetImp, GenSeq<n>>
::impl(std::forward<Ts>(args)...);
}
此外,还有一篇关于获取第 n 个类型的实现的非常有趣的文章:
感谢他们的工作,我不知道我们以前可以使用(...(来做黑客。
- 在C++中,如何通过几种类型从元组中选择多个元素
- 如何为 c++ 的不同变量类型的结构元素创建动态数组?
- 在 C++ 中输出枚举类类型的向量元素
- C++默认情况下,指针类型数组的元素是否保证初始化为 nullptr?
- 如何使用模板根据类型将元素添加到各种容器中
- C++ 未初始化的本地(非全局)int 数组中的元素类型到底是什么?
- OpenCV C++:当垫子类型未知时无法访问垫子元素?
- C++如何乘以包含 std::variant 元素的向量的迭代器?正在执行迭代器类型的转换?
- C++:是否可以编写一个函数,将不同类型的元素附加到变体数组中?
- C++通过别名指针以静默方式将错误的类型分配给数组元素
- 我无法在用forward_as_tuple创建的元组中按类型访问元素
- 如何告诉自动推断向量<bool>元素的非引用类型
- 为什么将函数的返回类型从结构节点*更改为void后,链表的元素没有显示create_ll和显示?
- 如何为基类通用类型创建向量以使用具体类型元素
- 按索引将类类型元素从一个向量复制到另一个 c++
- 如何实现数据结构,就像C 中的数组一样支持不同的类型元素
- 如何在构造具有不同类型元素的向量副本时显式转换
- 处理特定类型元素的任何容器的非模板函数
- *类型元素容器的qt排序
- 如何将两个双数据类型元素转换为一个Point数据类型