什么是创造"瘦"的正确方法?一个对象的struct包装器?

时间:2017-10-12 07:01:36

标签: c++ c++11

我正在玩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;
}

我在clang和VS中得到了这个:

0
3
3
3

这是在gcc:

3
3
3
3

在VS中,我可以看到vector<int>{1,2,3}的析构函数是在创建z后调用的。所以我猜我上面的例子是未定义的行为。 reversion_wrapper保存对已销毁的r值的引用。所以我的问题是:

  1. 我的结论是否正确?如果没有,为什么编译器会产生不同的输出?为什么clang把这个尺寸归零?另外,我猜想w也是未定义的行为。
  2. 构造一个接受r值和l值的struct wrapper的正确方法是什么,如果可能的话,保持对象的常量被包裹?
  3. 编辑1

    我可以将r值变量绑定到const&amp;所以我想知道这样的事情是否会起作用?

    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;
    };
    

1 个答案:

答案 0 :(得分:2)

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

是的,使用z.iterable是由于僵尸引用而未定义的行为(此处不会发生临时生命周期扩展,因为没有向量&lt;&gt;临时也没有绑定的引用)

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

这没关系,移动(c)只是将c转换为vector<int>&&,w.iterable referers转换为c,但请注意没有任何移动。

  

构造一个接受r值和l值的struct wrapper的正确方法是什么,如果可能的话,保持对象的常量被包裹?

关于对象的生命周期,给定一个'纯'包装器(即持有引用的东西),你不能。您始终需要确保没有悬挂引用。当然,你总是可以让你的包装复制 / move-construct rvalues,但这很少有用。

如果问题是如何传递保留其非/ const l / rvaluesness的参数,则称为完美转发。但是,这不是你想要的,因为你的包装器存储rvalue引用没什么意义。

类似

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

会这样做(remove_reference&lt;&gt;在这里不是绝对必要的,但它为包装器参数提供了更合理的选择)。此外,如果您想完全禁用右值(例如,如果您希望您的包装器永远不会与临时使用一起使用),则可以选择它。在这种情况下,你可以在reverse()内部使用static_assert()或在(const T&amp;&amp;)中删除=或使用SFINAE来过滤掉过载。

  

我可以将r值变量绑定到const&amp;所以我想知道这样的事情是否会起作用?

对于T&amp; T来说,过载更容易/更清洁和T const&amp;在那种情况下:

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

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

但我不明白为什么你会这么想。