What is the correct way to create a “thin” shell structure for an object?

I play with the answer to this question and I get different results between clang and gcc. With the following code:

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

I get this in clang and VS:

 0 3 3 3 

and this is in gcc:

 3 3 3 3 

In VS, I see that the destructor for vector<int>{1,2,3} is called after creating z . Therefore, probably, my example above is undefined. reversion_wrapper contains a reference to the corrupted r value. So my questions are:

  • Is my conclusion correct above? If not, why do compilers generate different output? and why does clang nullify the size? In addition, I would suggest that w is also undefined.
  • What would be a suitable way to build a wrapper for a structure that takes r and l-values, if possible, while maintaining the constant of the streamlined object?

change 1

I can bind an r-value variable to const & so I'm wondering if there would be something like this?

 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; }; 
+5
source share
1 answer
 auto z = reverse(vector<int>{1, 2, 3}); 

yes, using z.iterable is undefined behavior due to a reference to zombies (there is no temporary extension of lifespan because there is no vector <> temporary or a link to a binding)

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

this is normal, move (c) just translates c into vector<int>&& , w.iterable referers to c, but notice that nothing is moved.

What would be a suitable way to build a wrapper for a structure that takes r and l-values, if possible, while maintaining the constant of the streamlined object?

regarding the lifetime of an object, given the “clean” shell (ie, something containing links), you cannot. You should always ensure that no dangling links appear. Of course, you can always allow copy / move-construct rvalues ​​to your instances, but this is rarely useful, I would say.

if the question is how do you pass an argument that preserves its non / const l / rvaluesness, it is called perfect forwarding. But this is not what you want here, because it makes no sense for your shell to store the rvalue link.

so something like

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

would do (the strict remove_reference <> condition is not required here, but this makes a more reasonable choice for a shell parameter). Moreover, it is your choice if you want to disable rvalues ​​at all (for example, if you expect your wrapper to never be used with temporary ones). In this case, you will either be static_assert () inside reverse () or = delete for (const T & &), or use SFINAE to filter the overload.

I can bind an r-value variable to const & so I'm wondering if there would be something like this?

easier / cleaner to overload T & and T const & in this case:

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

but I don’t understand why you need it.

+2
source

Source: https://habr.com/ru/post/1272545/


All Articles