我是元编程的新手。我看过其他类似的问题,但没有一个问题符合我的要求。
这是我尝试反转std :: tuple。我的主要问题是反转输入元组中的数据。
反转指数的逻辑并不合适,我无法从这个阶段开始。
到目前为止的代码:
//===========================================================!
// type inversion of a tuple
template < typename Tuple, typename T >
struct tuple_push;
template < typename T, typename ... Args >
struct tuple_push<std::tuple<Args...>, T>
{
typedef std::tuple<Args..., T> type;
};
template < typename Tuple >
struct tuple_reverse;
template < typename T, typename ... Args >
struct tuple_reverse<std::tuple<T, Args...>>
{
typedef typename tuple_push<typename tuple_reverse<std::tuple<Args...>>::type, T>::type type;
};
template < >
struct tuple_reverse<std::tuple<>>
{
typedef std::tuple<> type;
};
//===========================================================!
template <typename First, typename ...Tails>
auto inverse(std::tuple<First, Tails...> & data)
-> decltype(tuple_reverse<std::tuple<First,Tails...>>::type)
{
using reverse_tup = tuple_reverse<std::tuple<First, Tails...>>::type;
static_assert(false, "Confused!")
return reverse_tup();
}
期待一个紧凑而简单的解决方案。
答案 0 :(得分:7)
这是使用C ++ 14的可能解决方案:
template <typename T, std::size_t... indices>
auto invert(T &&tuple, std::index_sequence<indices...>) {
// Using decay_t as the argument must be a tuple, and this shortens the code
using tuple_t = std::decay_t<T>;
constexpr auto tuple_size = std::tuple_size<tuple_t>{};
return std::tuple<std::tuple_element_t<tuple_size - indices - 1, tuple_t>...>(
std::get<tuple_size - indices - 1>(std::forward<T>(tuple))...);
}
template <typename T>
auto invert(T &&tuple) {
return invert(std::forward<T>(tuple),
std::make_index_sequence<std::tuple_size<std::decay_t<T>>{}>());
}
Demo。
对于C ++ 11,可以使用相同的过程,但必须提供诸如make_index_list
之类的辅助模板。
答案 1 :(得分:1)
反转指数的逻辑并不合适,我无法从这个阶段开始。
我认为你指的是使用std::index_sequence
(C ++ 14)的解决方案,
或类似的,such as Jonathan Wakeley's',
同样@Columbo的解决方案也是不合适的,即使它也是如此
这是一个C ++ 11。
可能你不喜欢&#34;反转指数的逻辑&#34;因为你认为它 有一个不必要的运行时成本。它没有运行时成本。它在编译时执行。
更有可能的是,你知道但只是认为这种解决方案不够优雅,而不是 像你喜欢的那样简单或紧凑。
嗯,你知道这个用于反转序列S
的经典递归算法:连续取S
项
并将它们推到另一个的前面,最初为空序列S'
。最后,S'
与S
相反。
几乎没有什么比这更简单,这里是一个紧凑的C ++ 11实现,应用于元组。
假设标题"tuple_plus.h"
提供元函数tuple_reverse
的现有定义
及其先决条件。
#include "tuple_plus.h"
namespace detail {
template <size_t I, class Wip, typename ...Ts>
Wip
inverse(std::tuple<Ts...> const & model, Wip && wip = std::tuple<>(),
typename std::enable_if<(I >= sizeof...(Ts))>::type * = nullptr)
{
return wip;
}
template <size_t I, class Wip, typename ...Ts>
typename tuple_reverse<std::tuple<Ts...>>::type
inverse(std::tuple<Ts...> const & model, Wip && wip = std::tuple<>(),
typename std::enable_if<(I < sizeof...(Ts))>::type * = nullptr)
{
return
inverse<I + 1>(model,std::tuple_cat(std::make_tuple(std::get<I>(model)),wip));
}
} // namespace detail
template<typename ...Ts>
typename tuple_reverse<std::tuple<Ts...>>::type
inverse(std::tuple<Ts...> const & tup)
{
return detail::inverse<0,std::tuple<>>(tup);
}
当然,我们不能循环而不是元组元素,因为它们只能通过std::get<I>(tup)
访问,
对于常量索引I
。因此,实施不能像std::vector<T>
那样紧凑。我们
必须遵循索引常量的模板元递归的通常模式。我们需要一对SFINAE重载,
当I
具有限制值(I >= sizeof...(Ts)
)时编译器选择一个,而I
具有其他任何其他选项时选择另一个
值(I < sizeof...(Ts)
)。然后我们需要实现您真正想要的功能模板
template<typename ...Ts>
typename tuple_reverse<std::tuple<Ts...>>::type
inverse(std::tuple<Ts...> const & tup);
作为此实现的包装器来初始化和隐藏递归参数。但有了这些
不可避免的安排,这是经典的序列反转算法,适用于C ++ 11中的元组,
没有任何index_sequence
拐杖。
N.B。该声明与您帖子中的声明略有不同: -
它将接受0长度的元组std::tuple<>
,以及更长的元组。这个
更好,因为你的返回类型tuple_reverse<Tuple>::type
的通用定义
专门用于std::tuple<>
。所以这种方式无论何时定义函数
return-type是。
参数传递为const &
,而不只是&
。你不打算修改
输入元组,因此该函数应接受std::tuple<Ts...> const
个参数。
现在,我将解释为什么这种优雅的解决方案与非常有效的解决方案相比较
在index_sequence
设备上。
这是一个可以测试元组反转解决方案的程序 实现所需的界面。
#include "tuple_plus.h"
#include <type_traits>
///////////////////////////////////////////////////
//
// Paste your implementation here
//
///////////////////////////////////////////////////
///////////////////////////////////////////////////
// Testing it...
#include <iostream>
using namespace std;
struct item
{
static unsigned ctor_count;
static unsigned copy_count;
item()
: id(ctor_count++) {}
item(item const & other)
: id(other.id) {
++copy_count;
}
item & operator=(item const & other) {
id = other.id;
++copy_count;
return *this;
}
static void clear() {
ctor_count = copy_count = 0;
}
unsigned id;
};
unsigned item::ctor_count;
unsigned item::copy_count;
ostream & operator<<(ostream & out, item const & e)
{
return out << "item #" << e.id;
}
template<unsigned I = 0, typename ...Ts>
typename enable_if<(I >= sizeof...(Ts))>::type
tuple_print(tuple<Ts...> const &)
{
cout << "\n";
}
template<unsigned I = 0, typename ...Ts>
typename enable_if<(I < sizeof...(Ts))>::type
tuple_print(tuple<Ts...> const & tup)
{
cout << '[' << get<I>(tup) << "] ";
tuple_print<I + 1>(tup);
}
template<unsigned I>
void test()
{
auto tup_tup =
tuple<
tuple<>,
tuple<item>,
tuple<item,item>,
tuple<item,item,item>,
tuple<item,item,item,item>,
tuple<item,item,item,item,item>,
tuple<item,item,item,item,item,item>>();
item::clear();
auto const & tup = get<I>(tup_tup);
cout << "In..." << endl;
tuple_print(tup);
cout << "Out..." << endl;
tuple_print(inverse(tup));
cout << "To invert a " << I << "-element tuple\n"
<< "I copied " << item::copy_count << " elements\n";
}
int main()
{
test<0>(),test<1>(),test<2>(),test<3>(),test<4>(),test<5>(),test<6>();
return 0;
}
将经典解决方案剪切并粘贴到:
// Paste your implementation here
然后使用-std=c++11
进行编译并运行。输出运行经典算法
长度为0到6的标本元组,并在每种情况下显示a)算法的结果
确实反转了元组和b)反转元组的成本,用元组复制操作来衡量。
In...
Out...
To invert a 0-element tuple
I copied 0 elements
In...
[item #20]
Out...
[item #20]
To invert a 1-element tuple
I copied 3 elements
In...
[item #19] [item #18]
Out...
[item #18] [item #19]
To invert a 2-element tuple
I copied 7 elements
In...
[item #17] [item #16] [item #15]
Out...
[item #15] [item #16] [item #17]
To invert a 3-element tuple
I copied 12 elements
In...
[item #14] [item #13] [item #12] [item #11]
Out...
[item #11] [item #12] [item #13] [item #14]
To invert a 4-element tuple
I copied 18 elements
In...
[item #10] [item #9] [item #8] [item #7] [item #6]
Out...
[item #6] [item #7] [item #8] [item #9] [item #10]
To invert a 5-element tuple
I copied 25 elements
In...
[item #5] [item #4] [item #3] [item #2] [item #1] [item #0]
Out...
[item #0] [item #1] [item #2] [item #3] [item #4] [item #5]
To invert a 6-element tuple
I copied 33 elements
调查费用的顺序:
0, 3, 7, 12, 18, 25, 33,...
你可以满足自己的功能:
Cost(n) = (n squared + 5n) / 2
所以这个算法在元组大小上是二次方的。
如果你想要练习,你可以找出另一个的元组实现 用于反转序列的库存递归算法。不那么简单或 紧凑就像这一样,它就是那个:交换第一个和最后一个元素 在你已经颠倒了它们之间的序列之后。成本可能会低一些,但仍然是二次方。
这令人痛苦,因为众所周知,扭转一系列事物
是线性问题:例如,经典算法的明显C ++改编
转换std::vector
会有Cost(n) = 4n
。
众所周知的事实背后隐含的假设是序列是a
对于某些Container<T>
和Container
,T
;所以反转算法是免费的
在一个位置修改包含的序列,而不在其他位置修改它,全部
同时还有Container<T>
。
但是,扭转std::tuple
的麻烦就是
std::tuple<....,A,....,B,....>
与类型不同
std::tuple<....,B,....,A,....>
,std::tuple<....,A,....>
。所以你不能
实际上通过推送另一个元素来反转std::tuple
tup
tup
的前面;或者在 A
中交换 B
和 tup
等。
要在tup
上模拟任何此类成员操作,您必须构建一个整体
新元组,不同类型,复制所有 tup
的元素,甚至更多。
要拿起香蕉,你必须抬起整个大猩猩。
考虑到这一点,将@Columbo的实现粘贴到测试程序中。你&#39; 11
需要将代码中的invert
更改为inverse
。编
与std=c++14
并运行。 (对于此选项,您需要gcc 4.9或clang 3.5,
和gcc 4.9将发出一个不重要的编译器警告。
现在输出是:
In...
Out...
To invert a 0-element tuple
I copied 0 elements
In...
[item #20]
Out...
[item #20]
To invert a 1-element tuple
I copied 1 elements
In...
[item #19] [item #18]
Out...
[item #18] [item #19]
To invert a 2-element tuple
I copied 2 elements
In...
[item #17] [item #16] [item #15]
Out...
[item #15] [item #16] [item #17]
To invert a 3-element tuple
I copied 3 elements
In...
[item #14] [item #13] [item #12] [item #11]
Out...
[item #11] [item #12] [item #13] [item #14]
To invert a 4-element tuple
I copied 4 elements
In...
[item #10] [item #9] [item #8] [item #7] [item #6]
Out...
[item #6] [item #7] [item #8] [item #9] [item #10]
To invert a 5-element tuple
I copied 5 elements
In...
[item #5] [item #4] [item #3] [item #2] [item #1] [item #0]
Out...
[item #0] [item #1] [item #2] [item #3] [item #4] [item #5]
To invert a 6-element tuple
I copied 6 elements
费用的顺序是0,1,2,3,4,5,6,...
。对于此解决方案,Cost(n) = n
:
它是最有效的。
在此解决方案中使用index_sequence
设备是 the essense
其最佳效率。如果要避免二次运行时成本
通过模拟成员操作来反转tup
,唯一的选择是
只需构造反转元组,用元素初始化
已经在编译时从tup
中的对应者映射了。{
映射:
Index I -> tuple_size - I - 1
在运行时,没有任何反应,但反转元组的构造, 它很快就会完成。
但是要在编译时执行该映射,您必须拥有 indices 的列表
编译时tup
的元素。这就是index_sequence
设备提供。
如果C ++ 14绝对禁止,你可以接受一个flyweight C ++ 11替代品
std::index_sequence
courtesty of Andy Prowl
有了这个,你可以有以下解决方案:
namespace detail {
// This is...
template<int... Is>
struct seq { };
template<int N, int... Is>
struct gen_seq : gen_seq<N - 1, N - 1, Is...> { };
template<int... Is>
struct gen_seq<0, Is...> : seq<Is...> {};
// ... the flyweight index_sequence apparatus.
// You can put it in a header.
template<typename Tuple, int ...Is>
typename tuple_reverse<typename std::decay<Tuple>::type>::type
invert(Tuple && tup, seq<Is...>)
{
return
std::make_tuple(
std::get<std::tuple_size<
typename std::decay<Tuple>::type>::value - Is - 1>
(std::forward<Tuple>(tup))...);
}
} //namespace detail
template<typename ...Ts>
typename tuple_reverse<std::tuple<Ts...>>::type
inverse(std::tuple<Ts...> const & tup)
{
return detail::invert(tup,detail::gen_seq<(sizeof...(Ts))>());
}
在测试程序中,其输出与@ Columbo相同。
道德:std::index_sequence
(或更一般地,std::integer_sequence
)
是一个非常优雅和基本的工具,可以在C ++中有效地使用TMP。
这就是为什么它在C ++ 14标准库中的原因。 (顺便说一下,Jonathan
Wakeley is its author`)