I am playing around with the answer to this question and I am getting 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 in gcc:
3
3
3
3
In VS I can see that the destructor for vector<int>{1,2,3}
is being called after z
is created. So I guess my example above is undefined behavior. reversion_wrapper holds a reference to a destroyed r-value. So my questions are:
edit 1
I can bind r-value variable to const& so I am wondering if something like this wouldn't work?
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});
yes, using z.iterable is undefined behaviour due to zombie reference ( no temporary lifetime extension occurs here because there's no vector<> temporary nor a reference to bound to )
vector<int> c{ 1, 2, 3 };
auto w = reverse(move(c));
this is ok, move(c) simply casts c to vector<int>&&
, w.iterable referers to c, but note that nothing is moved.
What would be the proper way to construct a struct wrapper that accepts r-values and l-values, if possible keeping the constness of the object being wrapped?
regarding object lifetime, given a 'pure' wrapper ( that is, something holding references ) you can't. You always need to make sure no dangling references occur. Of course, you can always let your wrapper copy/move-construct rvalues , but this is seldom useful I'd say.
if the question is how you pass an argument preserving its non/const l/rvaluesness, it's called perfect forwarding. But, it's not what you want here because it makes little sense for your wrapper to store an rvalue reference.
so something like
template <typename T>
reversion_wrapper<std::remove_reference_t<T>> reverse( T&& iterable ) { return { iterable }; }
would do ( the remove_reference<> is not strictly necessary here, but it makes a more reasonable choice for the wrapper parameter ). Moreover, it's your choice if you want to disable rvalues altogether ( eg. if you expect your wrapper to be never used with temporaries ). In this case, you would either static_assert() inside reverse() or =delete for (const T&&) or use SFINAE to filter out the overload.
I can bind r-value variable to const& so I am wondering if something like this wouldn't work?
it's easier/cleaner to overload for T& and T const& in that 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 cannot see why you would want that.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With