2014-03-30 66 views
2

我在D中編寫了一個基本的Node結構體,它被設計用作樹狀結構的一部分。代碼如下:使用struct構造函數的怪異行爲

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)

這是沒有意義的,我 - 爲什麼它試圖調用單參數構造函數時給出兩個參數?

+3

問題在於ref,你不能從臨時的IIRC中接受ref –

+0

如果問題確實是ref,你可以用'this()(自動引用節點u,auto ref Node v )'爲它工作。 注意使用'auto ref'來啓用傳遞臨時函數,但爲了這個功能,函數必須是一個模板。這就是爲什麼添加第一組括號。 – yaz

回答

3

該問題與構造函數無關。它與通過ref有關。您正在嘗試使用

this(ref Node u, ref Node v) {...} 

的構造由ref接受它的參數。這意味着它們必須是左值(即可以在任務左側的東西)。但是你傳遞函數調用的結果不會返回ref(因此,它返回一個臨時值,這是一個右值 - 可以在賦值的右邊,但不在左邊)。所以,你試圖做的是非法的。現在,錯誤信息並不是很好,因爲它給出了第一個構造函數而不是第二個構造函數的錯誤,但不管怎麼說,你沒有一個與你想要做的匹配的構造函數。目前,我可以考慮3個選項:

  1. 擺脫對構造函數參數的ref。如果你只是想通過函數調用的結果,就像你現在正在做的那樣,接受ref不會幫助你。返回的值將被移動到函數的參數中,因此不會發生副本,並且ref不會購買任何東西。當然,將返回值分配給局部變量,以便您可以將它們傳遞給構造函數,因爲它現在被編寫爲會讓您失去某些東西,因爲那樣會造成不必要的副本。

  2. 重載構造函數,以便它接受ref或non-ref。例如

    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. 對構造函數進行模板化並使用auto ref。這本質上是做什麼#2,但你只需要編寫一次該函數:

    this()(auto ref Node u, auto ref Node v) {...} 
    

    這將然後生成函數的副本匹配給定(最多4個不同的版本,它與全面的參數函數體中的每一個而不是其中的三個轉發到第四個),但是你只需要寫一次。在這種特殊情況下,因爲你正在處理一個結構,所以對這個函數進行模板化可能是合理的。如果Node是一個類,它可能沒有意義,因爲模板化函數不能是虛擬的。

所以,如果你真的能夠通過ref通過,那麼在這種特殊情況下,你應該去與#3和模板化的構造和使用auto ref。但是,我個人不會打擾。我只是去#1。你的使用模式不會從auto ref得到任何東西,因爲你總是傳遞給它兩個rvalues,而你的Node結構也不是很大,所以雖然你顯然不想複製它,如果你不需要要複製一個左值,將它傳遞給構造函數可能不會有太大的關係,除非你在做lot。但是,如果你將它傳遞給一個左值,那麼你只會得到一個副本,因爲右值可以被移動而不是被複制,並且你現在只傳遞它的右值(至少在這裏顯示的代碼) 。所以,除非你正在做的與構造函數不同的東西會涉及到左值,否則無需擔心左值 - 或者從函數返回並傳遞給構造函數時複製節點(因爲這是一個移動,而不是副本)。因此,刪除ref將是最好的選擇。

+0

優秀且非常有幫助的答案 - 謝謝! –