template specialization and rvalue reference, c++ [duplicate] - c++

This question already has answers here:
Specializing function template for reference types
(3 answers)
Closed 6 months ago.
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?

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

Ambiguity about rvalue reference passed and lvalue passed arguments on templated function [duplicate]

This question already has answers here:
Forwarding references for non-reference types
(2 answers)
Closed 3 years ago.
Consider this:
void test(int&& param){/*...*/}
int main(){
int a{444};
test(a);
}
Certainly It'll not compile because there's no conversion is defined for int to int&&. But the following code:
template<typename T>
void test(T&& param){/*..*/}
int main(){
int a=826;
test(a);
return 0;
}
will compile. But why? What's the reason? There's nothing special about the later except that function test will instantiates for all types T. But we still need to pass rvalue as parameter. So why does it,the later, compile? - without any complaint. Then I tried the following:
template<typename T>
void test(T&& param){/*..*/}
template<typename T>
void test(T param){/*...*/}
int main(){
int a=826;
test(a);
return 0;
}
The compiler complains because function call is ambiguous on whether calling test(T&& param) with T=int& or test(T param) with T=int.
Now I am totally baffled on what to deduce. What special does the Template do in here? Why doesn't it conform with the rules on rvalues and lvalues?
Thanks in Advance
T&& in this case are sometimes called universal reference or forwarding reference. But the case is that is you're falling in a specific template type deduction rule. The type of param in this case will be int&, then you're not binding a lvalue into a rvalue.

C++ constructor template specialization

I'm trying to create a specialized constructor for std::string arguments, but the other one is always used when I call it with a string argument.
struct Literal : Expression
{
template <typename V>
Literal(V val)
{
value = val;
}
};
template <>
Literal::Literal(std::string const& val)
{
value = val.c_str();
}
It doesn't matter if both are defined inside the class, both outside the class, or like in the posted example only the specialization is defined outside the class: When called with std::string, the assignment value = val gives a compiler error.
How do I correctly specialize this constructor template for std::string?
You don't.
You should overload the constructor: Literal(const std::string&), which you can do in the struct declaration.
The compiler always tries to match non-template overloads before template ones.
According to the standard, 14.8.2.1 Deducing template arguments from a function call [temp.deduct.call] where P is the template parameter and A is the function-call argument in that position:
2 If P is not a reference type:
If A is an array type, the pointer type produced by the array-to-pointer = standard conversion ([conv.array]) is used in place of A for type deduction; otherwise,
If A is a function type, the pointer type produced by the function-to-pointer standard conversion ([conv.func]) is used in place of A for type deduction; otherwise,
If A is a cv-qualified type, the top-level cv-qualifiers of A's type are ignored for type deduction.
If P is a cv-qualified type, the top-level cv-qualifiers of P's type are ignored for type deduction. If P is a reference type, the type referred to by P is used for type deduction. [...]
So given
std::string s{"hello"};
const std::string& sr{s};
Literal l(sr);
A (sr) is const std::string& but the constness is not considered, so the compiler considered std::string. This matches your
template <typename V>
Literal(V val)
{
value = val;
}
and so it uses this specialization. If you had specialized
template<>
Literal(std::string val)
the compiler would find this specialization, and this is probably what you will have to do and use move semantics.
#include <iostream>
#include <string>
struct S {
template<typename T>
S(T t) { std::cout << "T t\n"; }
std::string value_;
};
template<>
S::S(std::string value) {
std::cout << "string\n";
value_ = std::move(value);
}
template<>
S::S(const std::string&) {
std::cout << "const string&\n";
}
int main() {
S s1(42);
std::string foo{"bar"};
const std::string& foor = foo;
S s2(foo);
S s3(foor);
}
http://ideone.com/eJJ5Ch
Many times, when overload is a solution, people try to define full specialization. But overload could be a much better solution. In your case, I would create a new constructor with the string parameter.
Remember that only base template is considered in overload resolution. The following article is a good reference to understand this idea:
http://www.gotw.ca/publications/mill17.htm
UPDATE:
Anyway, to improve my answer, you could try the following base template constructor:
template <typename V>
Literal(V const& val)
{
value = val;
}

Template references type deducion

I have a template function:
template<typename T>
void doSomething(T& value) {
// doSomething here
}
All is ok, but passing r-value references:
doSomething(getTempVal());
Producing no matching function for call error, because particular instantiated function template expects an l-value for 1st argument. Is there any workarounds to allow template function taking both lvalue and rvalue references without adding new template?
Yes, just use && instead of &.
It may seem odd, because you might think that this would disallow calling it with lvalues, but actually, if T is itself an lvalue reference type, then T && is just T: it doesn't become an rvalue reference type.
In other words,
template <typename T>
void f(T &&);
int main() {
int k = 1;
f(k); // okay: calls f<int&>
f(2); // okay: calls f<int>
}
Note that T can be deduced as a reference type, and the function body will need to be made to handle that.

Overload resolution and universal reference parameters

The following code works and the overloads are found as expected:
struct HasBuzz
{
void buzz() const {}
};
struct NoBuzz {};
template <typename T>
void foo(T const& t)
{
t.buzz();
}
void foo(NoBuzz const&){}
int main()
{
foo(HasBuzz{});
foo(NoBuzz{});
}
However, if I replace the first overload with a "universal reference" version then it no longer works. The correct overload is not found for NoBuzz.
struct HasBuzz
{
void buzz() const {}
};
struct NoBuzz {};
template <typename T>
void foo(T&& t)
{
t.buzz();
}
void foo(NoBuzz const&){}
int main()
{
foo(HasBuzz{});
foo(NoBuzz{}); // error: NoBuzz has no member function buzz
}
What can I do to make it work?
Simple solution
Add an overload that is callable with an rvalue of type NoBuzz.
void foo(NoBuzz const&){ };
void foo(NoBuzz&&) { }; // overload for rvalues
Note: Depending on your actual use case this might not be enough, because if you pass a non-const lvalue type NoBuzz to foo you'd still instantiate the template, since the two NoBuzz overloads doesn't match. At the end of this post is a more complex, but certainly cleaner, solution.
Explanation
template<class T>
void foo (T&&); // (A)
void foo (NoBuzz const&); // (B)
The problem with your snippet is that your template (A) can be instantiated in such a way that it's a better match than your overload (B).
When the compiler sees that you are trying to call a function named foo with an argument which is an rvalue of type NoBuzz, it will look for all functions named foo taking one argument where a NoBuzz would fit.
Let's say it starts of with your template (A), here it sees that T&& is deducable to any reference type (both lvalue, and rvalue), since we are passing an rvalue T = NoBuzz.
With T = NoBuzz the instantiated template would be semantically equivalent to:
void foo (NoBuzz&&); // (C), instantiated overload of template (A)
It will then continue to your overload (B). This overload accepts a const lvalue reference, which can bind to both lvalues and rvalues; but our previous template instantiation (C) can only bind to rvalues.
Since (C) is a better match than (B), binding rvalues to T&& is prefered over U const&, that overload is selected and you get the behavior you describe in your post.
Advanced solution
We can use a technique called SFINAE to conditionally make it impossible to call the template if the type passed doesn't implement .buzz ().
template <typename T>
auto foo(T&& t) -> decltype (t.buzz ())
{
return t.buzz();
}
The above solution uses a lot of new features of C++11, detailed information is available here:
wikipedia.org - C++11 - auto
wikipedia.org - decltype
Conditional overloading with trailing-return-type possible?
refp's answer explains the behavior you're seeing, and offers a possible solution. Another option is to ensure the foo function template does not make it into the candidate set for overload resolution unless T has a member function named buzz().
template <typename T>
auto foo(T&& t)
-> decltype((void)(t.buzz()), void())
{
t.buzz();
}
After making this change the foo(NoBuzz const&) overload will be selected when you pass it an instance of NoBuzz. Live demo
A detailed explanation of what's going on in the decltype expression in the trailing return type can be found here. The only thing I've done differently here is instead of using three subexpressions, with the middle one being void() to prevent a user defined operator, from being selected, I've cast the result of the first expression to void; the intent and result are identical in both cases.

deducing references to const from rvalue arguments

Okay, this may seem like a silly question, but here it goes:
template <typename T>
void foo(T& x)
{
}
int main()
{
foo(42);
// error in passing argument 1 of 'void foo(T&) [with T = int]'
}
What is preventing C++ to instantiate the foo function template with T = const int instead?
The problem is that template type deduction has to work out an exact match, and in that particular case, because of the reference in the signature, an exact match requires an lvalue. The value 42, is not an lvalue, but rather an rvalue, and resolving T with const int would not yield a perfect match. Since template type deduction is limited to exact matches, that deduction is not allowed.
If instead of using a literal you use a non mutable lvalue, then the compiler will deduce the type appropriatedly, as const int will become a perfect match for the argument:
const int k = 10;
foo( k ); // foo<const int>( const int & ) is a perfect match
Now there is a special rule that enables calling a function that takes a const reference (nonmutable lvalue) with an rvalue, that implies creation of a temporary lvalue which is later bound to the reference, but for that rule to kick in the function has to have that signature before hand, which is why explicitly stating that the type of the template is const int works: foo<const int>(42).
Them's the rules ;-). If you leave the compiler to deduce the type from the argument, it picks the simplest thing it can.
It doesn't seem unreasonable to me. Your template is saying it expects a non-const reference, so it doesn't compile with an rvalue.
You could either tell it what you mean at the call site: foo<int const>(42); or change your template to make it clear it doesn't need a mutable reference: template <typename T> void foo(T const & x) { }.
In C++11 you have more options for expressing what your template will and will not accept.
Be it template or normal functions, rvalue cannot be passed by reference. (so const T& works but not T&).
What is preventing C++ to instantiate the foo function template with T = const int instead?
Suppose, C++ allows and makes T = const int instead.
Now after sometime you change foo as,
template<typename T>
void foo (T& x)
{
x = 0;
}
Now compiler has to generate error. For end user the experience will be strange, as for a valid statement like x = 0; it started giving error. That could be the reason that why compiler prevents at the first stage itself!