2017-10-12 59 views
5

我正在玩弄this question的答案,並且我得到了clang和gcc之間不同的結果。用下面的代碼:爲對象創建「瘦」結構包裝的正確方法是什麼?

#include <iostream> 
#include <vector> 

using namespace std; // for rbegin() and rend() 

template <typename T> 
struct reversion_wrapper { T& iterable; }; 

template <typename T> 
auto begin(reversion_wrapper<T> w) { return rbegin(w.iterable); } 

template <typename T> 
auto end(reversion_wrapper<T> w) { return rend(w.iterable); } 

template <typename T> 
reversion_wrapper<T> reverse(T&& iterable) { return { iterable }; } 

int main() { 

    auto z = reverse(vector<int>{1, 2, 3}); 
    cout << z.iterable.size() << '\n'; 

    vector<int> a{ 1, 2, 3 }; 
    auto x = reverse(a); 
    cout << x.iterable.size() << '\n'; 

    const vector<int> b{ 1, 2, 3 }; 
    auto y = reverse(b); 
    cout << y.iterable.size() << '\n'; 

    vector<int> c{ 1, 2, 3 }; 
    auto w = reverse(move(c)); 
    cout << w.iterable.size() << '\n'; 

    return 0; 
} 

我得到這個鐺和VS:

0 
3 
3 
3 

,這在GCC:

3 
3 
3 
3 

在VS,我可以看到,對於vector<int>{1,2,3}的析構函數在創建z後調用。所以我想我上面的例子是未定義的行爲。 reversion_wrapper保存對被銷燬的r值的引用。所以我的問題是:

  1. 我的結論是否正確?如果不是,爲什麼編譯器會生成不同的輸出?爲什麼鏗鏘聲大小?另外,我猜測w也是未定義的行爲。
  2. 構建一個接受r值和l值的結構包裝的正確方法是什麼?如果可能,保持對象的常量被包裝?

編輯1

我可以綁定R值變量爲const &所以我想,如果這樣的事情是行不通的?

template <typename T> 
struct reversion_wrapper { 
    static bool const rvalue; 

    using U = typename std::conditional_t<std::is_rvalue_reference_v<T>, const remove_reference_t<T>&, T&>; 
    U iterable; 
}; 
+0

有趣的問題。我在標準的某個地方見過,那個被const引用引用的臨時對象只要引用它就可以存活。這裏的問題是1)你的引用被包裝在另一個對象中,2)你通過一個函數調用並且3)這個標準可能意味着在定義點直接實現。你的推理看起來是正確的,但我們仍然可以長時間討論標準的含義。 – Tomek

回答

2
auto z = reverse(vector<int>{1, 2, 3}); 

是,使用z.iterable是不確定的行爲由於殭屍參考

vector<int> c{ 1, 2, 3 }; 
auto w = reverse(move(c)); 
(沒有臨時壽命延長,因爲沒有矢量<>臨時也不是參考結合到這裏發生)

這是可以的,移動(c)只需將c轉換爲vector<int>&&,即可將c引用到c,但請注意沒有任何移動。

構建一個接受r值和l值的結構包裝的正確方法是什麼?如果可能的話保持對象的常量被包裝?

關於對象生命週期,給定一個'純粹'包裝器(即,持有引用的東西),你不能。你總是需要確保沒有懸掛引用發生。當然,你可以隨時讓你的包裝拷貝/移動構造 rvalues,但這很少用,我會說。

如果問題是你如何傳遞一個參數來保存它的non/const l/rvaluesness,那就叫做完美轉發。但是,這不是你想要的,因爲你的包裝存儲一個右值引用是沒有意義的。

所以像

template <typename T> 
reversion_wrapper<std::remove_reference_t<T>> reverse(T&& iterable) { return { iterable }; } 

會做(的remove_reference <>並非絕對必要在這裏,但它使一個更合理的選擇。包裝參數)。此外,如果您想要完全禁用右值(例如,如果您期望您的包裝器從不使用臨時值),那麼這是您的選擇。在這種情況下,您可以在reverse()內部使用static_assert(),或使用= const delete(const T & &)或使用SFINAE過濾掉過載。

我可以將r值變量綁定到const &所以我想知道是否這樣的東西不起作用?

它在這種情況下,更容易/清潔超載對於T &和T常量&:

template <typename T> 
reversion_wrapper<T> reverse(T& iterable) { return { iterable }; } 

template <typename T> 
reversion_wrapper<const T> reverse(T const& iterable) { return { iterable }; } 

,但我不明白你爲什麼會想這一點。

+0

同意你的觀點。可能是一個糟糕的設計,使這個「通用」的東西。無論如何,現在我很好奇,如果我能以某種方式讓它工作。你能看看我的編輯嗎? (問題的結尾) – Mac

+0

@Mac,它更容易/更清潔地重載reverse()T&和const T&...我將編輯答案 –

相關問題