Search code examples
c++11rvalue-reference

C++11 rvalue reference and const-ness


The following code is a snippet of a tuple-like class where it is possible to get a reference to a given type in the tuple, or if that type is not found, the provided default value will be returned instead.

If the default value is a lvalue a reference must be returned, if the default value is a rvalue then a rvalue must be returned.

The following code illustrates the problem I'm having:

struct Foo {
    Foo(int d) : data(d) {}

    template <typename T, typename TT>
    const TT get_or_default(TT&& t) const {
        return data;
    }

    template <typename T, typename TT>
    TT get_or_default(TT&& t) {
        return data;
    }

    int data;
};

int main(int argc, char* argv[]) {
    int i = 6;
    const Foo foo1(5);
    Foo foo2(5);

    // compile error
    foo1.get_or_default<int>(i);

    // works
    foo1.get_or_default<int>(5);
    foo2.get_or_default<int>(i) = 4;
    foo2.get_or_default<char>('a');

    return 0;
}

When compiling this I get the following error:

cxx.cxx:6:20: error: binding of reference to type 'int' to a value of type 'const int' drops qualifiers
            return data;
                   ^~~~
cxx.cxx:23:14: note: in instantiation of function template specialization 'Foo::get_or_default<int, int &>' requested here
        foo1.get_or_default<int>(i);
             ^
1 error generated.

Solution

  • There is a special rule for template argument deduction when the function parameter is of type T&& where T is a template parameter. That rule is:

    If the function argument is an lvalue of type U, then U& is used in place of U for type deduction in this case.

    It's used to allow perfect forwarding. Basically, it means that T&& for a template parameter T is a "universal reference."

    In your case, since i is indeed an lvalue, TT is deduced to int&. Applying a const to that is ignored (it would apply to the reference itself, not to the type referred to), so the fucntion instantiated from the template looks something like this:

    int& get_or_default(int& t) const {
      return data;
    }
    

    And since the function is const, data is considered const as well and so it cannot bind to a non-const reference.