std::tuple为C++11引入的特性,它可以容纳任意类型的元素,同时具有编译期计算的特性,在元编程中也有广泛的应用,在之前博文【3】中也利用tuple实现了默认模板参数的扩展,本文结合其他实践来介绍tuple的应用。
基础功能
tuple创建与元素获取
using boost::typeindex::type_id_with_cvr;
int main()
{
int n = 1;
auto t = std::make_tuple(10, "Test", 3.14, n);
std::cout << "type:" << type_id_with_cvr<decltype(t)>().pretty_name() << std::endl;
std::cout << "The value of t is ("
<< std::get<0>(t) << ", "
<< std::get<1>(t) << ", "
<< std::get<2>(t) << ", "
<< std::get<3>(t) << ")\n";
}
输出
type:std::tuple<int, char const*, double, int>
The value of t is (10, Test, 3.14, 1)
通过std::make_tuple创建tuple对象,对应的类型std::tuple<int, char const*, double, int>;
也可以通过std::tuple<int, int, std::string> t {0, 1, "Test"}直接创建对象;
如果希望创建的是类型引用,则可以修改:
int n = 1;
auto t = std::make_tuple(10, "Test", 3.14, std::ref(n));
// 对n修改则tuple中的也会发生改变
输出:
std::tuple<int, char const*, double, int&>
通过std::get获取指定位置的元素;
遍历
实现类似std::for_each进行遍历tuple中的元素:
struct Functor {
template<typename T>
void operator()(T&& t)
{
std::cout << t << std::endl;
}
};
int main()
{
auto t = std::make_tuple(513, "Test", 3.14);
TupleForeach(Functor(), t);
}
输出:
513
Test
3.14
TupleForeach实现:
template<size_t N>
struct TupleForeachHelper {
template<typename F, size_t I, size_t ...Is, typename TupleType>
static void Execute(F& f, std::index_sequence<I, Is...> index, TupleType& tup)
{
f(std::get<I>(tup));
TupleForeachHelper<sizeof...(Is)>::Execute(f, std::index_sequence<Is...> {}, tup);
}
};
template<>
struct TupleForeachHelper<0> {
template<typename F, size_t ...Is, typename TupleType>
static void Execute(F& f, std::index_sequence<Is...> index, TupleType& tup) {}
};
template<typename F, typename ...Args>
void TupleForeach(F&& f, std::tuple<Args...>& tup)
{
TupleForeachHelper<sizeof...(Args)>::Execute(f, std::make_index_sequence<sizeof...(Args)> {}, tup);
}
借助std::index_sequence的非类型模板参数,将tup中的元素递归依次获取进行遍历。
打印
通过TupleForeach也能实现打印的功能,但如果仅仅只是打印则可以通过可变参表达式进行简化:
namespace Detail {
template<size_t ...Is, typename TupleType>
void TupelPrintImpl(std::index_sequence<Is...>, TupleType& tup)
{
int a[] = {(std::cout << std::get<Is>(tup) << ", ", 0)...}; // 可变参表达式
std::cout << std::endl;
}
}
template<typename ...Args>
void TuplePrint(std::tuple<Args...>& tup)
{
Detail::TupelPrintImpl(std::make_index_sequence<sizeof...(Args)>{}, tup);
}
输出:
513, Test, 3.14,
拼接
int main()
{
std::tuple<int, std::string, float> t1(10, "Test", 3.14);
int n = 7;
auto t2 = std::tuple_cat(t1, std::make_pair("Foo", "bar"), t1, std::tie(n));
n = 10;
TuplePrint(t2);
}
std::tuple_cat将多个tuple拼接成了一个新的tuple。
注:std::tie(n)返回的类型为std::tuple<int &>
获取元素类型
std::tuple<int, std::string, float> t1(10, "Test", 3.14);
std::cout << "type:" << type_id_with_cvr<std::tuple_element<2, decltype(t1)>::type>().pretty_name() << std::endl;
通过std::tuple_element便可以在编译期获取tup中指定位置的元素类型,tuple_element实现参考【2】:
template< std::size_t I, class T >
struct tuple_element;
// recursive case
template<std::size_t I, class Head, class... Tail >
struct tuple_element<I, std::tuple<Head, Tail...>>
: std::tuple_element<I-1, std::tuple<Tail...>> { };
// base case
template< class Head, class... Tail >
struct tuple_element<0, std::tuple<Head, Tail...>> {
typedef Head type;
};
应用实践
函数Pipeline
通过stuple实现函数pipeline串联:
template<typename ...Funs>
class FunChain {
public:
FunChain() : m_funTuple(std::tuple<> {}) {}
FunChain(std::tuple<Funs...> funTuple) : m_funTuple(funTuple) {}
template<typename F>
auto Then(F& f)
{
return FunChain<Funs..., F>(std::tuple_cat(m_funTuple, std::make_tuple(f)));
}
template<typename ...Args>
auto operator()(Args&& ...args)
{
return Call(std::make_index_sequence<sizeof...(Funs)> {}, std::forward<Args>(args)...);
}
private:
template<std::size_t I, std::size_t ...Is, typename ...Args>
auto Call(std::index_sequence<I, Is...>, Args&&... args)
{
return Call(std::index_sequence<Is...> {}, std::get<I>(m_funTuple)(std::forward<Args>(args)...));
}
template<std::size_t I, typename ...Args>
auto Call(std::index_sequence<I>, Args&&... args)
{
return std::get<I>(m_funTuple)(std::forward<Args>(args)...);
}
std::tuple<Funs...> m_funTuple;
};
int Fun1(int val)
{
return val + 2;
}
int Fun2(int val)
{
return val + 3;
}
auto val = (fc.Then(Fun1).Then(Fun2))(0); // val = 5
基于FunChain添加Fork:
int Fun3(int val1, int val2)
{
return val1 + val2;
}
auto val = (fc.Then(Fun1).Fork(Fun2, Fun2).Then(Fun3))(0); // val = 10
通过fork实现将Fun1的结果分发给两个Fun2、Fun2,而将两个Fun2输出作为Fun3的输入,即Fun3有两个入参,因此val1 = 5,val2 = 5,最终val = 10; 修改之后完整的参考实现代码如下:
template<typename ...Args>
struct ForkRetType {
using Type = std::tuple<typename FunctionTraits<Args>::RetType...>;
};
template<typename ...Funs>
class FunFork {
public:
FunFork(Funs&...funs) : m_funTuple(std::make_tuple(funs...)) {}
using TupRetType = typename ForkRetType<std::decay_t<Funs>...>::Type;
template<typename ...Args>
TupRetType operator()(Args&& ...args)
{
return Call(std::make_index_sequence<sizeof...(Funs)>(), std::forward<Args>(args)...);
}
private:
template<std::size_t ...Is, typename ...Args>
auto Call(std::index_sequence<Is...>, Args&&... args)
{
return std::make_tuple(std::get<Is>(m_funTuple)(std::forward<Args>(args)...)...);
}
std::tuple<Funs...> m_funTuple;
};
template<typename ...Funs>
class FunChain {
public:
FunChain() : m_funTuple(std::tuple<> {}) {}
FunChain(std::tuple<Funs...> funTuple) : m_funTuple(funTuple) {}
template<typename F>
auto Then(F f)
{
return FunChain<Funs..., F>(std::tuple_cat(m_funTuple, std::make_tuple(f)));
}
template<typename ...CallerTypes>
auto Fork(CallerTypes... callers)
{
return FunChain<Funs..., FunFork<CallerTypes...>>(std::tuple_cat(m_funTuple,
std::make_tuple(FunFork<CallerTypes...>(callers...))));
}
template<typename ...Args>
auto operator()(Args&& ...args)
{
return Call(std::make_index_sequence<sizeof...(Funs)> {}, std::forward<Args>(args)...);
}
private:
template<typename CallerType, size_t ...Is, typename ...Args>
auto TupleApply(CallerType&& caller, std::index_sequence<Is...>, std::tuple<Args...>& tup)
{
return caller(std::get<Is>(tup)...);
}
template<typename Caller, typename ...Args>
auto CallHelper(Caller caller, std::tuple<Args...>& tup)
{
return TupleApply(caller, std::make_index_sequence<sizeof...(Args)> {}, tup);
}
template<typename Caller, typename ...Args>
auto CallHelper(Caller caller, Args&&... args)
{
return caller(std::forward<Args>(args)...);
}
template<std::size_t I, std::size_t ...Is, typename ...Args>
auto Call(std::index_sequence<I, Is...>, Args&&... args)
{
auto nextArgs = CallHelper(std::get<I>(m_funTuple), std::forward<Args>(args)...);
return Call(std::index_sequence<Is...> {}, nextArgs);
}
template<std::size_t I, typename ...Args>
auto Call(std::index_sequence<I>, Args&&... args)
{
return CallHelper(std::get<I>(m_funTuple), std::forward<Args>(args)...);
}
std::tuple<Funs...> m_funTuple;
};
将需要Fork的函数对象封装到ForkType可调用对象中,示意图如下:
函数仅对入参进行逻辑处理,本身并无修改程序状态,相同的输入总是能得到相同的结果;
参考资料
【1】函数式编程
【2】https://en.cppreference.com/w/cpp/utility/tuple
【3】Default Arguments总结
【4】深入应用C++11-代码优化与工程级应用