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.
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
, thenU&
is used in place ofU
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.