2017-10-12 56 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&...我将编辑答案 –

相关问题