1

Code

#include <iostream>

using namespace std;

#define PF         cout << __PRETTY_FUNCTION__ << endl;

class berlp {
public:
    berlp() { }
    void p() { }
};

template <typename T>
class derp {
public:
    derp() = default;

    derp(const T & a) : mem(a) {
        a.p();
        mem.p();

        PF
    }

    template <typename U>
    derp(U && a) : mem(std::forward<U>(a)) {
        PF
    }

    T       mem;
};

int main(int argc, const char * argv[])
{
    berlp                   one;
    derp<berlp &>           f(one);     // problems with this list below
    derp<const berlp &>     h(one);     // problem with this follows

    return 0;
}

Output Using XCode and CLang This all compiles fine, and here is the output...

derp<berlp &>::derp(const T &) [T = berlp &]
derp<const berlp &>::derp(U &&) [T = const berlp &, U = berlp &]

Problems

derp<berlp &> f(one); : a.p() in the derp constructor should fail since "a" is "const berlp &" after reference collapsing, and p() is not const. Secondly, initialization of "mem" (berlp &) with "a" (const berlp &) should not work. It seems like the "const" in "derp(const T & a)" is just not doing anything. Finally, why does it even use the first constructor and not the templated one which seems like it would do all of this without breaking the const?

derp<const berlp &> h(one); : why does this call use the templated constructor when the other seems to be exactly what we are after? This isn't too terrible a problem, since it doesn't seem to break anything, but it does let you modify the passed berlp in the constructor, while the other constructor (supposedly) shouldn't.

So, I'm either terribly confused, or something is up! Please help!

pat
  • 763
  • 4
  • 12
  • Some reading about universal references and overloading: http://mortoray.com/2013/06/03/overriding-the-broken-universal-reference-t/ – goji Oct 01 '13 at 04:35
  • Also this question: http://stackoverflow.com/questions/18264829/universal-reference-vs-const-reference-priority – goji Oct 01 '13 at 04:40

1 Answers1

1

There are really multiple questions here:

  1. Why does derp<berlp&>(one) use the first constructor?

    The declaration of the constructor is derp(T const&) which turns into derp(berlp& const&) and is collapsed into derp(berlp&) as there is no such thing as a const reference or a reference reference. This stated in 8.3.2 [dcl.ref] paragraph 6:

    If a typedef (7.1.3), a type template-parameter (14.3.1), or a decltype-specifier (7.1.6.2) denotes a type TR that is a reference to a type T, an attempt to create the type “lvalue reference to cv TR” creates the type “lvalue reference to T”, while an attempt to create the type “rvalue reference to cv TR” creates the type TR.

    Obviously, passing a berlp& to a constructor taking a berlp& is an exact match and the template constructor can't do any better. Thus, the non-template constructor is chosen.

  2. Why does calling derp<berlp&>(one) with a berlp work?

    There is no real surprise here: mem is of type berlp& and initialized with a berlp& so the non-const members all work as expected.

  3. When using derp<berlp const&> and passing a berlp& the template constructor is a perfect match and is, obviously, chosen. The member variable of type berlp const& is just initialized with a berlp& which implicitly converts to a berlp const&. No surprise here, either.

I think you are just a bit confused about the reference collapsing rules. Placing the const into the wrong location also doesn't help: putting it to the right should actually clearly up most of the confusion and is part of my preference to put the const to the right.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380