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



我正在研究这个问题的答案,我在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值的结构包装器的正确方法是什么

编辑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;
};
auto z = reverse(vector<int>{1, 2, 3});

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

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

这是可以的,move(c)只是将c强制转换为vector<int>&&,将w.iterable-referers强制转换为c,但请注意,没有移动任何内容。

如果可能的话,构造一个接受r值和l值的结构包装器的正确方法是什么?

关于对象生存期,如果给定一个"纯"包装器(即包含引用的东西),则不能。您总是需要确保没有悬空引用发生。当然,你总是可以让你的包装器复制/移动构造右值,但我认为这很少有用。

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

所以像这样的东西

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

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

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

相关内容

最新更新