是否有任何方法(例如,修改参数类型)使以下'cons'函数占用恒定时间而不是O(n)时间。即建立列表应该花费O(n)时间,而不是O(n ^ 2)时间。
我可以在以下条件下这样做:
(1)无动态内存分配
(2)x必须仍然有效(即可能没有提及临时工具)
(3)HEAD类型可能有一个单独编译的构造函数(不可嵌入)
#include <type_traits>
template <typename HEAD, typename TAIL>
struct List
{
List(HEAD head, TAIL tail) : head(head), tail(tail) {}
typename std::remove_reference<HEAD>::type head;
typename std::remove_reference<TAIL>::type tail;
};
template <typename HEAD, typename TAIL>
List<HEAD, TAIL> cons(HEAD head, TAIL tail)
{
return List<HEAD, TAIL>(head, tail);
}
struct Empty {};
int main()
{
auto x = cons(1, cons(2, cons(3, Empty())));
// x should still be valid here
}
如何工作的例子。
编译器知道x的类型,因此在堆栈上分配空间。
所以堆栈看起来像这样:
| Int1 | Int2 | Int3 |
| p | p+4 | p+8 |
p
是任意地址。
编译器创建调用cons(2, cons(3, Empty()))
,将返回值定向到p+4
。
在cons(2, cons(3, Empty()))
内,编译器创建对cons(3, Empty())
的调用,将返回值定向到p+8
。
这样,每次调用cons
时,都不需要复制tail
。
我只是不确定代码,因此编译器可以(例如,允许)进行此优化。如果有另一种获得恒定运行时间的方法,我会很乐意使用它。
答案 0 :(得分:2)
你似乎正在用更糟糕的std::tuple
助手重新发明std::make_tuple
。改用它。标准不会为std::tuple
或std::make_tuple
的转发构造函数提供复杂性保证,但它有点没有实际意义,因为这两者使用完美转发,因此每个元素只需要构建一个move / copy / emplace构造致电std::make_tuple
;剩下的就是改组参考文献了。它至少是线性数量的结构。
当然,并不能保证你的编译器能够优雅地处理所有的引用改组,但无论如何你都会在错误的级别进行优化。
出于说明目的,几乎但不完全发生了什么:
template<typename... T>
class tuple {
T... members; // this is not correct, only here for illustration
// The forwarding constructor
template<typename... U>
explicit
tuple(U&&... u)
: member(std::forward<U>(u)...)
{}
};
template<typename... T>
tuple<typename std::decay<T>::type...>
make_tuple(T&&... t)
{ return tuple<typename std::decay<T>::type...>(std::forward<T>(t)...); }
因此,在致电auto tuple = std::make_tuple(1, 2, 3)
时,从int
的调用中有三个临时make_tuple
,然后在第一次调用{{1}时有三个int&&
个x值在std::forward<int>(t)...
内部绑定到构造函数的参数,这些参数再次作为make_tuple
xvalues转发给int&&
的概念性三个成员,这三个成员是从它们构造的。
刚才意识到我所说的构造数量仅适用于std::tuple<int, int, int>
的调用,而不是整个表达式std::make_tuple
。由于从函数返回元组,它可能需要移动/ RVO到最终变量。它仍然是线性的,它仍然是编译器喜欢优化的东西之一,它仍然是担心优化的错误点。
然而,C ++ 0x充满了善意,它已经可以完成您在答案中描述的内容:
auto tuple = std::make_tuple(...);
对int i = 3;
std::tuple<int, int, int> tuple = std::forward_as_tuple(1, 2, i);
的来电将返回forward_as_tuple
。这个帮助器永远不会返回带有值的元组,只返回引用的“浅”元组。 std::tuple<int&&, int&&, int&>
的适当转换构造函数将通过两个移动和一个副本初始化其“成员”。请注意,这种愚蠢(非)优化会使您有可能编写std::tuple<int, int, int>
,这是一个包含两个悬空引用的元组。
答案 1 :(得分:0)
我会在调查后回答自己的问题,但如果有人知道更好的方法,我仍然会感兴趣。
(1)将List
重命名为TempList
(2)使TempList
私有的构造函数(包括移动和复制)
(3)让cons
成为TempList
的朋友
(4)将HEAD
和TAIL
成员引入参考文献。
现在TempList
只保存引用,因此没有副本。这些可能是对临时工具的引用,但是没关系,因为临时工具持续到表达式的结尾,并且因为TempList
只有私有构造函数,所以它不能分配给表达式的LHS。只有cons
可以创建TempList
,并且不能超过创建它的表达式。
现在创建一个函数save
或具有TempList
效果的函数,并返回一个真实的List
,另一种类型的数据按值存储。
所以我们有
auto x = save(cons(1, cons(2, cons(3, Empty()))));
数据将被复制或移动一次(通过保存),整个构造现在为O(n)
。只要内联TempList
和cons
,save
结构就可能会被优化掉。