为对象创建"瘦"结构包装器的正确方法是什么?

Mac*_*Mac 5 c++ c++11

我正在玩这个问题的答案,我在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;
}
Run Code Online (Sandbox Code Playgroud)

我在clang和VS中得到这个:

0
3
3
3
Run Code Online (Sandbox Code Playgroud)

这在gcc中:

3
3
3
3
Run Code Online (Sandbox Code Playgroud)

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

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

编辑1

我可以将r值变量绑定到const&so我想知道这样的东西是不行的吗?

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;
};
Run Code Online (Sandbox Code Playgroud)

Mas*_*nes 2

auto z = reverse(vector<int>{1, 2, 3});
Run Code Online (Sandbox Code Playgroud)

是的,由于僵尸引用,使用 z.iterable 是未定义的行为(这里没有发生临时生命周期扩展,因为没有向量 <> 临时也没有绑定到的引用)

vector<int> c{ 1, 2, 3 };
auto w = reverse(move(c));
Run Code Online (Sandbox Code Playgroud)

没关系,move(c) 只是将 c 转换为vector<int>&&c 的 w.iterable 引用,但请注意,没有任何内容被移动。

如果可能的话保持被包装对象的常量性,构造接受右值和左值的结构包装器的正确方法是什么?

关于对象生命周期,给定一个“纯”包装器(即保存引用的东西),你不能。您始终需要确保不会出现悬空引用。当然,您始终可以让包装器复制/移动构造右值,但我想说这很少有用。

如果问题是如何传递一个参数并保留其非/常量左值/右值,则称为完美转发。但是,这不是您想要的,因为包装器存储右值引用没有什么意义。

所以像

template <typename T>
reversion_wrapper<std::remove_reference_t<T>> reverse( T&& iterable ) { return { iterable }; }
Run Code Online (Sandbox Code Playgroud)

会做(remove_reference<>在这里并不是绝对必要的,但它为包装器参数做出了更合理的选择)。此外,如果您想完全禁用右值(例如,如果您希望包装器永远不会与临时变量一起使用),这是您的选择。在这种情况下,您可以在reverse()或=delete for (const T&&)中使用static_assert(),或者使用SFINAE来过滤掉过载。

我可以将 r 值变量绑定到 const& 所以我想知道这样的东西是否行不通?

在这种情况下,对 T& 和 T const& 进行重载会更容易/更干净:

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

template <typename T>
reversion_wrapper<const T> reverse( T const& iterable ) { return { iterable }; }
Run Code Online (Sandbox Code Playgroud)

但我不明白你为什么想要那个。