6

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?

fjanisze
  • 1,234
  • 11
  • 21
  • only doing std::move does not solve this problem, see my answer below about the fully specialized template functions not participating in overload resolution – Andreas Loanjoe Aug 18 '22 at 09:30
  • @AndreasLoanjoe Your answer is completely wrong. The reason for why the generic version is called is because `std::forward(stuff)` is an expression and is of type `std::vector` and not `std::vector&&` so that `T` will be deduced as `std::vector` which means the generic version should be called as it correctly does. The same is explained [here](https://stackoverflow.com/a/73400309/19414420) – Kal Aug 18 '22 at 09:52
  • @Kal Replacing std::forward by move here does not output Ok, because even if you pass in an RValue it will not work because the specialized function does not participate in overload resolution, there is another problem. – Andreas Loanjoe Aug 18 '22 at 10:05

2 Answers2

2

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.

Jason
  • 36,170
  • 5
  • 26
  • 60
  • @273K If by other answer you mean [this answer](https://stackoverflow.com/a/73400323/12002570) then in that case the nontemplate version which has a `cout<<"Ok` inside it will be called because a nontemplate version is preferred over the template one in case they are equally ranked. – Jason Aug 18 '22 at 10:05
  • Replacing forward by move here does not solve your problem because it will still prefer calling the generic version. Fully specialized template functions do not participate in overload resolution a non-generic function is required. – Andreas Loanjoe Aug 18 '22 at 10:09
  • @AndreasLoanjoe Yes, i already know that. I already explained the same thing in one of my comment that i have now deleted. And my answer also makes that clear. – Jason Aug 18 '22 at 10:16
-2

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));
}
Andreas Loanjoe
  • 2,205
  • 10
  • 26
  • No, this is not the reason for the output. Correct reason is explained [here](https://stackoverflow.com/a/73400309/12002570). – Jason Aug 18 '22 at 09:11
  • The point about specializing function templates is useful: https://www.modernescpp.com/index.php/full-specialization-of-function-templates – Hari Aug 18 '22 at 09:20
  • This answer is completely wrong. – Kal Aug 18 '22 at 09:47
  • No idea what you guys are talking about. What is wrong about it? I simplified the explanation of why move is required over forward here without the in depth reference collapsing description. – Andreas Loanjoe Aug 18 '22 at 10:12
  • @AndreasLoanjoe No, you've completely missed the point which is that the specialization is for `std::vector&&` but `T` gets deduced to `std::vector` so the specialization cannot be used. Moreover, there is no need to use `std::move` as the question is asking for "why OP is getting the output they're getting" and not that "how to get a different output". Additonally, your old answer was wrong too. – Jason Aug 18 '22 at 10:26
  • I'm mentioning that std::forward collapses to std::vector, what do you mean? – Andreas Loanjoe Aug 18 '22 at 10:32
  • @AndreasLoanjoe If `t` is of non-reference object type `T`, then all three of `std::forward(t)`, `std::forward(t)` and `std::move(t)` are equivalent and produce a xvalue, so there is no problem with using `std::forward(stuff)` in the question, although it is unconventional. It is unclear whether OP expects that the specialization participates in overload resolution as an additional candidate or whether they expect that the primary template deduces `T` to `mytype&&` and then uses the specialization. (Neither is true.) You and JasonLiam seem to disagree on which it is. – user17732522 Aug 18 '22 at 13:11