CuriousGeorge
CuriousGeorge

Reputation: 7400

When does && mean 'forwarding reference'?

When compiled, the following code causes this error:

'Container::Wrapper::Wrapper(S)': member function already defined or declared

Does the compiler think S&& in the constructor of Wrapper is a forwarding reference?

template<typename T>
struct Container
{
    template<class S>
    struct Wrapper {
        S obj;
        Wrapper(S&& obj) : obj(std::forward<S>(obj)){}
        Wrapper(const S& obj) : obj(obj){}
    };

    template<class S>
    void push_back(S&& obj) {
        void *storage = malloc(sizeof(Wrapper<S>));
        new (storage) Wrapper<S>(std::forward<S>(obj));
    }
};

struct Foobar{};

int main()
{
    Container<Foobar> cont;
    Foobar foobar;
    cont.push_back(foobar);
    return 0;
}

Looking at an example from here, I don't understand how what I'm doing is any different:

template <class T, class Allocator = allocator<T> >
class vector {
public:
    ...
    void push_back(T&& x);       // fully specified parameter type ⇒ no type deduction;
    ...                          // && ≡ rvalue reference
};


Edit: The solution to this issue was to modify push_back to remove the reference from the type being used to instantiate the Wrapper:

template<class S>
void push_back(S&& obj) {
    typedef std::remove_reference<S>::type U;
    void *storage = malloc(sizeof(Wrapper<U>));
    new (storage) Wrapper<U>(std::forward<S>(obj));
}

Upvotes: 3

Views: 132

Answers (1)

AnT stands with Russia
AnT stands with Russia

Reputation: 320621

There are no universal references involved it the implementation of Wrapper. The only universal reference in your code is Container<>::push_back's parameter.

When you call cont.push_back(foobar);, parameter S of push_back is deduced as Foobar &.

Later you attempt to instantiate your Wrapper<S> with S == Foobar &. Reference collapsing rules dictate that in Wrapper's constructor parameter declarations S && turns into Foobar & and const S & also turns into Foobar &.

This means that you end up with two supposedly "overloaded" Wrapper::Wrapper constructors, which have identical signatures. This is the reason for the error message you observe.

If you attempt to instantiate std::vector with a template argument of lvalue reference type, you will run into exactly the same problem for its push_back overloads. However, with such an attempt compilation will typically fail miserably for other reasons, well before it gets to push_back overloads.

A more distilled example would look as follows

template <typename T> struct MyVector {
  void foo(T &&) {}
  void foo(const T &) {}
};

int main() {
  MyVector<int &> v;
}

and will produce the same error.

The fairly non-obvious part here is that const T & with T = U & actually becomes U & and not const U &. But that's indeed the case.

Upvotes: 2

Related Questions