template specialization and rvalue reference, c++

Viewed 88

I'm a bit confused about this little example:

using mytype = std::vector<std::string>;

template<typename T>
void test(T item)
{
    throw std::runtime_error(typeid(item).name());
}
template<>
void test(std::vector<std::string>&& vec)
{
    std::cout<<"Ok."<<std::endl;
}

int main()
{
    mytype stuff;
    test(std::forward<mytype>(stuff));
}

I would expect the specialized template to be elected for the call here, but it's not, removing && will make that happen (and the argument is moved into vec)..

Why is the test version specialized for rvalue argument not being used?

2 Answers

Why is the test version specialized for rvalue argument not being used?

This is because the function argument std::forward<mytype>(stuff) that you're passing is an expression and an expression in C++ is never of some reference type. That is, the type of the function call argument std::forward<mytype>(stuff) is actually std::vector<std::string> and not std::vector<std::string>&&. In other words, T will be deduced as std::vector<std::string> and not std::vector<std::string>&&.

Basically, you've specialized the function template for the template argument std::vector<std::string>&& but T gets deduced to std::vector<std::string>. Thus, the specialization cannot be used. On the other hand, if you were to removed the && then the specialization will be called(see explanation at the end of the answer).

Lets look at a contrived example to clear this up:

Example 1

I am adding the following example to show that my above explanation is correct.

template <class T> void f(T)
{
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

void g() { 
    f((const int&)0);  //T will be deduced as int and not "const int" or even "const int&"
    f((int&&)0);       //T will be deduced as int and not "int&&"
}
int main()
{
    g();
    return 0;
}

Working demo.

Example 2

template<typename T>
void test(T item)        //#1
{
    std::cout<<"generic"<<std::endl;
}
template<>
void test(int&& vec)    //#2
{
    std::cout<<"Ok."<<std::endl;
}

int main()
{
    int stuff = 0;
    //---vvvvvvvvvvvvvvvvvvvvvvvv----------->the argument is an expression and is of type int instead of int&&
    test(std::forward<int>(stuff));         //calls #1
   
}

In the above example the expression std::forward<int>(stuff) is of type int and not int&&, therefore T is deduced as int(and not int&&). This means the generic version will be called.


removing && will make that happen

When you remove the &&, then this time you're explicitly specializing the function template for std::vector<std::string> and not std::vector<std::string>&&. This means that this time, the deduced T matches the template argument for which you've specialized the function template and so the specialization is called.

There are two problems here:

The first is that specialized functions do not participate in overload resolution.

The second is that std::forward does not convert your LValue to an RValue and correctly calls the generic function for type std::vector<std::string> resulting from the std::forward.

std::move takes an object of any type removes the reference and casts it as an rvalue reference std::forward casts to the value category (lvalue or rvalue) the caller used to pass it.

To turn an LValue into an RValue you have to use move. And to make sure that your "specialized" function actually participates in the overload resolution, replace the specialization by a non-generic function overload like so:

template<typename T>
void test(T item)
{
    throw std::runtime_error(typeid(item).name());
}
void test(std::vector<std::string>&& vec)
{
    std::cout<<"Ok."<<std::endl;
}

int main()
{
    std::vector<std::string> stuff;
    test(std::move(stuff));
}
Related