1

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. 
2
  • foo.get_or_default<int>(i); foo is not declared... Commented Mar 21, 2014 at 15:23
  • Ups this is a type, now fixed Commented Mar 21, 2014 at 23:35

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

4 Comments

The prototype looks like this: const TT get_or_default(TT&& t) const you deleted the const-ness of the return type.
@Allan I did not, and the answer addresses it. Re-read this: "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)." When you type const int &, it means "reference to constant int." When you type const TT, where TT is int&, it means "constant TT", i.e. something like "constant reference to int," which is really the same as "reference to int."
I see your point - sorry about the comment. But could you please suggest how I can work around this? It is important that a reference it returned when a lvalue is provided. I found a solution which includes writing my own add_cost helper class and use this to alter the return type, but I'm not sure if this is the right way of doing it (std::add_const will not add const to references).
@Allan I believe a custom type trait to keep int an int but turn int& into const int& is the way to go - there's nothing in the standard library that could do it for you.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.