我在D中编写了一个基本的Node
结构,旨在用作树状结构的一部分。代码如下:
import std.algorithm: min;
alias Number = size_t;
struct Node {
private {
Node* left, right, parent;
Number val;
}
this(Number n) {val = n;}
this(ref Node u, ref Node v) {
this.left = &u;
this.right = &v;
val = min(u.val, v.val);
u.parent = &this;
v.parent = &this;
}
}
现在,我编写了一个简单的函数,它应该给我一个Node
(意思是整个树),其中参数数组提供叶子,如下所示。
alias Number = size_t;
Node make_tree (Number[] nums) {
if (nums.length == 1) {
return Node(nums[0]);
} else {
Number half = nums.length/2;
return Node(make_tree(nums[0..half]), make_tree(nums[half..$]));
}
}
现在,当我尝试通过dmd运行它时,我收到以下错误消息:
Error: constructor Node.this (ulong n) is not callable using argument types (Node, Node)
这对我没有意义 - 为什么在给出两个参数时它会尝试调用一个参数的构造函数?
答案 0 :(得分:3)
问题与构造函数无关。它与ref
的传递有关。您尝试使用的构造函数
this(ref Node u, ref Node v) {...}
通过ref
接受其参数。这意味着它们必须是左值(即可以在赋值的左侧)。但是你传递的是函数调用的结果,它不会被ref
返回(所以,它返回一个临时的,这是一个rvalue - 可以在赋值的右边但不是左边)。所以,你要做的是非法的。现在,错误消息并不好,因为它给出了第一个构造函数而不是第二个构造函数的错误,但无论如何,您没有与您尝试执行的操作相匹配的构造函数。目前,我可以想到3个选项:
摆脱构造函数参数的ref
。如果你只是传递函数调用的结果,就像你现在正在做的那样,让它接受ref
无论如何都无法帮助你。返回的值将被移动到函数的参数中,因此不会进行任何复制,ref
不会为您购买任何东西。当然,将返回值分配给局部变量,以便您可以将它们传递给构造函数,因为它当前正在编写会失去一些东西,因为那样你就会制作不必要的副本。
重载构造函数,使其接受ref或non-ref。 e.g。
void foo(ref Bar b) { ... }
void foo(Bar b) { foo(b); } //this calls the other foo
一般来说,当你有一个参数时,这种方法运行得相当好,但这里有点烦人,因为你添加参数时最终会出现指数级的函数签名爆炸。所以,对于你的构造函数,你最终会得到
this(ref Node u, ref Node v) {...}
this(ref Node u, Node v) { this(u, v); }
this(Node u, ref Node v) { this(u, v); }
this(Node u, Node v) { this(u, v); }
如果你添加了第3个参数,你最终会得到 8个重载。因此,它实际上不会扩展到单个参数之外。
对构造函数进行模板化并使用auto ref
。这基本上做了#2所做的,但你只需要编写一次函数:
this()(auto ref Node u, auto ref Node v) {...}
然后,这将生成一个函数的副本以匹配给定的参数(最多4个不同的版本,每个版本中包含完整的函数体,而不是其中3个只转发到第4个),但是你只需要写一次。在这种特殊情况下,将函数模板化可能是合理的,因为你正在处理一个结构。如果Node是一个类,它可能没有意义,因为模板化的函数不能是虚拟的。
所以,如果真的希望能够通过ref
,那么在这种特殊情况下,您应该使用#3并对构造函数进行模板化并使用{{1 }}。不过,就个人而言,我不会打扰。我只想去#1。你的使用模式不会从auto ref
得到任何东西,因为你总是传递两个右值,而你的auto ref
结构不是很大,所以虽然你显然不想如果你不需要复制它,复制左值以将它传递给构造函数可能没那么重要,除非你做了 lot 。但同样,如果你传递一个左值,你只会得到一个副本,因为一个右值可以移动而不是复制,你现在只传递右值(至少用这里显示的代码) 。所以,除非你正在做一些不同的构造函数,它会涉及传递lvalues,所以没有必要担心左值 - 或者当节点从函数返回并传递给构造函数时被复制(因为那是一个移动,而不是副本)。因此,仅删除Node
将是最佳选择。