5

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:

  1. Is my conclusion above correct? If not, why do the compilers generate different output? and why is clang zeroing out the size? Also, I would guess that w is also undefined behavior.
  2. 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?

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;
};
Mac
  • 3,397
  • 3
  • 33
  • 58
  • Interesting question. I've seen somewhere in standard, that temporary object which is referenced by a const reference should survive as long as the reference which refers to it. The problem here is that 1) your reference here is wrapped in another object AND 2) you are taking it through a function calls AND 3) the standard likely meant the direct assingment at point of definition. Your reasoning seems correct but still we can have pretty long discussion about what the standard meant. – Tomek Oct 12 '17 at 07:13

1 Answers1

2
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.

Massimiliano Janes
  • 5,524
  • 1
  • 10
  • 22
  • Agree with your points. Probably a bad design to make something this "generic". Anyway, now I am curious if I could get it to work somehow. Can you have a look at my edit? (end of question) – Mac Oct 12 '17 at 08:35
  • @Mac, it's easier/cleaner to overload reverse() for T& and const T&... I'll edit the answer – Massimiliano Janes Oct 12 '17 at 08:45