Question

Consider the situation where a function template needs to forward an argument while keeping it's lvalue-ness in case it's a non-const lvalue, but is itself agnostic to what the argument actually is, as in:

template <typename T>
void target(T&) {
    cout << "non-const lvalue";
}

template <typename T>
void target(const T&) {
    cout << "const lvalue or rvalue";
}


template <typename T>
void forward(T& x) {
    target(x);
}

When x is an rvalue, instead of T being deduced to a constant type, it gives an error:

int x = 0;
const int y = 0;

forward(x); // T = int
forward(y); // T = const int
forward(0); // Hopefully, T = const int, but actually an error
forward<const int>(0); // Works, T = const int

It seems that for forward to handle rvalues (without calling for explicit template arguments) there needs to be an forward(const T&) overload, even though it's body would be an exact duplicate.

Is there any way to avoid this duplication?

Was it helpful?

Solution

This is a known problem and the purpose of rvalue references in C++0x. The problem has no generic solution in C++03.

There is some archaic historical reason why this occurs that is very pointless. I remember asking once and the answer depressed me greatly.

OTHER TIPS

In general, this nasty problem with templates ought to require duplication because the semantics where a variable is const or not, or a reference or not, are quite distinct.

The C++11 solution to this is "decltype" but it is a bad idea because all it does is compound and already broken type system.

No matter what the Standard or the Committee says, "const int" is not and will never be a type. Nor will "int&" ever be a type. Therefore a type parameter in a template should never be allowed to bind to such non-types, and thankfully for deduction this is the case. Unfortunately you can still explicitly force this unprincipled substitution.

There are some idiotic rules which try to "fix" this problem, such as "const const int" reducing to "const int", I'm not even sure what happens if you get "int & &": remember even the Standard doesn't count "int&" as a type, there is a "int lvalue" type but that's distinct:

int x;       // type is lvalue int
int &y = x;  // type is lvalue int

The right solution to this problem is actually quite simple: everything is a mutable object. Throw out "const" (it isn't that useful) and throw away references, lvalues and rvalues. It's quite clear that all class types are addressable, rvalue or not (the "this" pointer is the address). There was a vain attempt by the committee to prohibit assigning to and addressing rvalues.. the addressing case works but is easily escaped with a trivial cast. The assignment case doesn't work at all (because assignment is a member function and rvalues are non-const, you can always assign to a class typed rvalue).

Anyhow the template meta-programming crowd have "decltype" and with that you can find the encoding of a declaration including any "const" and "&" bits and then you can decompose that encoding using various library operators. This couldn't be done before because that information is not actually type information ("ref" is actually storage allocation information).

When x is an rvalue

But x is never an rvalue, because names are lvalues.

Is there any way to avoid this duplication?

There is a way in C++0x:

#include <utility>

template <typename T>
void forward(T&& x)   // note the two ampersands
{
    target(std::forward<T>(x));
}

Thanks to reference collapsing rules, the expression std::forward<T>(x) is of the same value-category as the argument to your own forward function.

Assuming there are k arguments, as I understand it the only "solution" in C++03 is to manually write out 2^k forwarding functions taking every possible combination of & and const& parameters. For illustration, imagine that target() actually took 2 parameters. You would then need:

template <typename T>
void forward2(T& x, T& y) {
    target(x, y);
}

template <typename T>
void forward2(T& x, T const& y) {
    target(x, y);
}

template <typename T>
void forward2(T const& x, T& y) {
    target(x, y);
}

template <typename T>
void forward2(T const& x, T const& y) {
    target(x, y);
}

Obviously this gets very unwieldy for large k, hence rvalue references in C++0x as other answers have mentioned.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top