#include <iostream>
using namespace std;
namespace mine {
template <typename T>
struct remove_rval {
using type = T;
};
template <typename T>
struct remove_rval<T&&> {
using type = T;
};
template <typename T>
void g(const T& = typename remove_rval<T>::type())
cout << __PRETTY_FUNCTION__ << endl;
}
}
int main()
{
mine::g<int&&>(); // doesn't work, because of explicit template?
const int& i2 = mine::remove_rval<int&&>::type(); // works, sanity check
return 0;
}
The function template I wrote fails to compile. From my understanding of c++, you can assign an rvalue to a constant lvalue reference. But, in this situation, it is like the deduced type disregards the 'const' qualifier when assigning the function default value. Why is this?
From dcl.ref/p6:
If a typedef-name ([dcl.typedef], [temp.param]) or a decltype-specifier ([dcl.type.decltype]) denotes a type TR that is a reference to a type T, an attempt to create the type lvalue reference to cv TR creates the type lvalue reference to T, while an attempt to create the type rvalue reference to cv TR creates the type TR.
Thus in your example, when T = int&& :
const T& collapses to T&(which isint&) and not const T&(which is const int&) according to the above quoted statement. And since we can't bind an rvalue like remove_rval<T>::type() to a non-const lvalue reference, you get the mentioned error.
Thus, even though the form of the function parameter in g is a reference to const T aka const lvalue reference(i.e., const T&), the first call to g instantiates g with a reference to non-const T aka non-const lvalue reference(i.e., T& =int&) as the parameter:
template<>
void g<int &&>(int&)
{
//operator<< called here
}
And since the parameter is int&, we cannot bind an rvalue like remove_rval<int&&>::type() to that parameter.
void g(const T& = typename remove_rval<T>::type())
Reference collapsing rules make const T& equal to U& when T = U&&. Lvalue-references can't bind to temporaries like remove_rval<T>::type(). You can instead simply pass int as a template argument and the parameter's type will correctly be const int&.
Related
I'm wondering why, in the following example, the function func has this signature:
void func<int&>(int&)
I know that named rvalue references are treated like lvalue references. I'm wondering why they aren't const lvalue references? I was expecting to see void func<const int&>(const int&) as a signature of func function.
template<typename T>
void func(T&& t) {}
int main()
{
int&& t = 5;
func(t);
}
Consider the following example:
template<typename T>
void f(T&) {};
int main(void){
const int i = 0;
const int& r = i;
f(r);
}
Let's name the template parameter type as P and the template argument type as A. Then, P and A will be:
P = T&, A = const int;
Since P is a reference type, [temp.deduct.call]/2 is not applied, but I think [temp.deduct.call]/3 is, which says:
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.
After applying the above quote, P and A will be:
P = T, A = const int; // T = const int;
I got stuck when I applied the same concept to this example:
template<typename T>
void g(const T&) {};
int main(void)
{
int i = 0;
g(i);
}
The types of the template parameter P and its corresponding template argument A are:
P = const T&, A = int;
After applying the above quote, P and A will be:
P = const T, A = int; // Is type deduction failed here?
Per my knowledge, T cannot be deduced because argument type A requires a qualification conversion to match the parameter type P. Am I correct to say that? if no, what's the main reason that causes deducing const T from int fails? Just if possible, provide a rule from the C++ standard for that. This is my first question.
But when I tested that code in g++11.2.0, the program compiles fine, and the instantiated function is g<int>(const int&).
So it's seemed that the T has been deduced as int; why? this is my second question.
Scott Meyers' Effective Modern C++ explains exactly this case in the Item 1: Understanding template type deduction, Case 1: ParamType is a Reference or Pointer, but not a Universal Reference.
Considering a code structure as below:
template <typename T>
void f(ParamType param);
f(expr);
Where we want to deduce T and ParamType from expr.
And given the case that ParamType is a reference (not a universal reference; it's const T& in your example).
The deduction would work as:
If expr's type is a reference, ignore the reference part.
Then pattern-match expr's type against ParamType to determine T.
For the particular case of:
template<typename T>
void g(const T&);
int i = 0;
g(i);
i's type (expr's type) is int.
Pattern matching int against const T& (ParamType) determines T's type as int.
I can try reverse engineering my answer with references to cppreference and The Standard:
cppreference: from Eljay's comment link:
The comment 3) If P is a reference type, the referenced type is used for deduction. tells you to adjust P's type to const T.
Then you have a detailed example of how deduction works under 1) If P is a reference type, the deduced A (i.e., the type referred to by the reference) can be more cv-qualified than the transformed A:.
template<typename T>
void f(const T& t);
bool a = false;
f(a); // P = const T&, adjusted to const T, A = bool:
// deduced T = bool, deduced A = const bool
// deduced A is more cv-qualified than A
The Standard: from temp.deduct.call/3: 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.. Example 3 below that text contains almost exactly your example, apart from the fact that the argument is const int.
template<class T> int f(const T&);
const int i = 0;
int n2 = f(i); // calls f<int>(const int&)
P's type is const T&.
Since P is a reference type, the type referred to by P, i.e. const T, is used for type deduction.
In your example, A's type is int. Deducing T as int would match P's adjusted type as const int.
Per my knowledge, T cannot be deduced because argument type A requires a qualification conversion to match the parameter type P. Am I correct to say that? This is my first question.
No, your assumption/understanding that a conversion from int to const int requires a qualification conversion is incorrect. An example of a qualification conversion would be something like from int* to const int*.
So it's seemed that the T has been deduced as int; why? this is my second question.
This can be understood from temp.deduct#call-4 which states:
In general, the deduction process attempts to find template argument values that will make the deduced A identical to A (after the type A is transformed as described above).
However, there are three cases that allow a difference:
4.1) If the original P is a reference type, the deduced A (i.e., the type referred to by the reference) can be more cv-qualified than the transformed A.
4.2) The transformed A can be another pointer or pointer-to-member type that can be converted to the deduced A via a function pointer conversion and/or qualification conversion.
(emphasis mine)
Now we can apply this to your example:
template<typename T>
void g(const T&) {};
int main(void)
{
int i = 0;
g(i);
}
In above, P is const T& so it is adjusted to const T. And since the passed argument is A = int, the deduced T will be int while the deduced A will be const int.
The same explanation can be found here:
template<typename T>
void f(const T& t);
bool a = false;
f(a); // P = const T&, adjusted to const T, A = bool:
// deduced T = bool, deduced A = const bool
// deduced A is more cv-qualified than A
Explain me, please, how it works?
Why double && works for lvalue and rvalue?
And why const double && don't work for lvalue?
template <typename U>
void function(U& var) {
std::cout << var << std::endl;
}
int main()
{
int var1 = 45;
function(var1);
function(45); //error
}
error: invalid initialization of non-const reference of type ‘int&’ from an rvalue of type ‘int’ function(45);
////////////////////////////////////////////////
template <typename U>
void function(const U& var) {
std::cout << var << std::endl;
}
int main()
{
int var1 = 45;
function(var1);
function(45);
}
////////////////////////////////////////////////
template <typename U>
void function(U&& var) {
std::cout << var << std::endl;
}
int main()
{
int var1 = 45;
function(var1);
function(45);
}
////////////////////////////////////////////////
template <typename U>
void function(const U&& var) {
std::cout << var << std::endl;
}
int main()
{
int var1 = 45;
function(var1); // error
function(45);
}
error: cannot bind ‘int’ lvalue to ‘const int&&’ function(var1);
double && does not work for lvalues. However, you don't have double && in your code, you have U && for a deduced U. That is special syntax for a so-called forwarding reference.
There's a special rule in C++ template deduction rules which says that when the parameter type is T&& where T is a deduced type, and the argument is an lvalue of type X, then X& is used instead of X for type deduction.
In your case, this means U is deduced to be int&, so the type of parameter var is collapsed from "int& &&" into int &.
You can learn more about this mechanism by searching for terms such as "forwarding reference" (historically called "universal reference") or "perfect forwarding."
Let's summarize the rules at first.
lvalues can be bound to lvalue-reference
lvalues can't be bound to rvalue-reference
rvalues can be bound to lvalue-reference to const
rvalues can't be bound to lvalue-reference to non-const
rvalues can be bound to rvalue-reference
lvalues can be bound to forwarding-reference (and preserve its value category)
rvalues can be bound to forwarding-reference (and preserve its value category)
The 1st case is lvalue-reference to non-const, then
function(var1); // rule#1 -> fine
function(45); // rule#4 -> fail
the 2nd case is lvalue-reference to const, then
function(var1); // rule#1 -> fine
function(45); // rule#3 -> fine
the 3rd case is forwarding-reference, then
function(var1); // rule#6 -> fine
function(45); // rule#7 -> fine
the 4th case is rvalue-reference, then
function(var1); // rule#2 -> fail
function(45); // rule#5 -> fine
It's worth noting the difference between rvalue-reference and forwarding-reference:
(emphasis mine)
function parameter of a function template declared as rvalue reference to cv-unqualified type template parameter of that same function template
In your example, function(var1) is going to deduce var1 usage as lvalue. To make it compatible with rvalue-based template, you have to help your compiler a bit:
template <typename U>
void function(const U&& var) {
std::cout << var << std::endl;
}
int main()
{
int var1 = 45;
function(std::move(var1)); // let convert lvalue to rvalue
function(45);
}
P.S. And yes, you should always ask yourself "do I really need that rvalue reference, or I'm perfectly fine with that old good lvalue ref?"
I have this code
#include <iostream>
size_t F()
{
return 0;
}
template <class Type, class... NextTypes>
size_t F(const Type& type, const NextTypes&... nextTypes)
{
if (!std::is_const<Type>::value)
return sizeof(type) + F(nextTypes...);
else
return F(nextTypes...);
}
int main()
{
int a = 0;
const int b = 0;
const size_t size = F(a,b);
std::cout << "size = " << size << std::endl;
return 0;
}
I'm trying to know in compilation time the total size of constant parameters and non const parameters. The current out put is 8, for some reason the compiler thinks b is not constant, I used typeid and decltype to print the types of a and b and indeed the output shows b is an int and not const int as I expected. What am I missing? Is it possible to separate a variadic set of arguments to const arguments and non const?
Consider this function template:
template<typename T>
void deduce(const T&);
If you let the compiler deduce a type for T from an argument expression, the deduced type will never be const: It will try to make the const T of the function parameter identical to the type of the argument expression used to call the function. For example:
struct cls {};
const cls c;
deduce(c) // deduces T == cls
By deducing T == cls, the compiler succeeds in making const T identical to the argument type const cls. There is no reason for the compiler to produce two different functions for const- and non-const argument types; the parameter type of the function template instantiation will be const-qualified in any case: you requested it by saying const T& instead of, say, T&.
You can deduce the const-ness of an argument by not cv-qualifying the function parameter:
template<typename T>
void deduce(T&);
However, this will fail to bind to non-const temporaries (rvalues). To support them as well, you can use universal references:
template<typename T>
void deduce(T&&);
This will deduce an lvalue-reference type for T if the argument is an lvalue, and no reference if the argument is an rvalue. The const-ness will be deduced correctly.
For example, if the argument has the type const A and is an lvalue, T will be deduced to const A&. The function parameter then is const A& &&, which is collapsed to const A& (an lvalue-reference). If the argument is an rvalue, T will be deduced to const A, and the function parameter becomes const A&& (an rvalue-reference).
Note that since T can be a reference in this case, you need to remove that before checking for const-ness: std::is_const< typename std::remove_reference<T>::type >::value.
I am reading Bjarne's Rvalue Reference Quick Look and came to the following example:
template <class T, class A1>
std::shared_ptr<T>
factory(A1& a1)
{
return std::shared_ptr<T>(new T(a1));
}
This is much better. If a const-qualified type is passed to the
factory, the const will be deduced into the template parameter (A1 for
example) and then properly forwarded to T's constructor.
I do not understand how ::factory() can accept a const reference. Bjarne just states that the const will be deduced into the template parameter. What exactly does this mean?
If you pass in an lvalue of type const X, then A1 will be deduced as const X, and you will get a function that looks like
std::shared_ptr<T> factory(const X& a1) { ... }
template type deduction normally happen in three cases:
Parameter type is a reference or pointer, but not a universal
reference Parameter type is a universal reference Parameter type
is a neither reference nor pointer
your case falls into non-universal reference parameters deduction. The rules will be:
if expression is a reference, ignore that
pattern-match expression's type against parameter type to determine type T
For example:
template<typename T>
void factory(T& param); // param is a reference
int x = 42; // int
const int cx = x; // copy of int
const int& rx = x; // ref to const view of int
factory(x); // 1, T = int, param's type = int&
factory(cx); // 2, T = const int, param's type = const int&
factory(rx); // 3, T = const int, param's type = const int&
Now you can see when you pass in const X it matches case 2 and if you pass in const X& it matches case 3.
Note: std::shared_ptr is the noise in this your sample, I remove it to demonstrate type deduction.