Why is this template expansion legal in C++? - c++

Consider the following code:
template <typename T>
void foo(const T& param) {
std::cout << __PRETTY_FUNCTION__ << std::endl;
std::cout << param;
}
int main()
{
foo<const int&>(5);
return 0;
}
The output gives:
void foo(const T&) [with T = const int&]
5
Here apparently T is resolved to const int& (as the printout says, since I explicitly forced so). However, this seems to create a problem: The function signature takes a parameter of type const T&, and in this case it would expand to const const int& &, which is not legal syntax in C++.
But the program indeed runs fine. What's going on here?
Update:
I know that T& & would collapse to T&. However, in this case, the double const is also not legal if you write them explicitly, and I don't see that template collapsing rule mention this part.
Update 2:
Compiling const const int x = 1; gives error: duplicate ‘const’. Multiple const is not allowed in c++ (but surprisingly in c it's okay).

C++ types are not formed by textual replacement. const T& where T is void * is void* const &, not const void *&.
Your code attempts to form the type "lvalue reference to const T" where T is "lvalue reference to const int". By the reference collapsing rules, that will instead form the type "lvalue reference to const int".

Related

Why constructor Message(const T& data) is conflict with Message(T&& data) when T = int&?

template <typename T>
struct Message {
T data;
explicit Message(T&& data) : data(std::move(data)) {
std::cout << "Move data" << std::endl;
}
explicit Message(const T& data) : data(data) {
std::cout << "Copy data" << std::endl;
}
};
template <typename T>
inline Message<T>*
makeMessage(T&& data) {
return new Message<T>{std::forward<T>(data)};
}
int main() {
const int a = 1024;
auto *copy_msg = makeMessage(a);
}
There is a template class Message that has two constructors: Message(T&& data) and Message(const T& data), And I got the following compile-time errors when I called makeMessage(a).
error: multiple overloads of 'Message' instantiate to the same signature 'void (const int &&)'
explicit Message(const T& data) : data(data) {
previous declaration is here
explicit Message(T&& data) : data(std::move(data)) {
However, It works when I called make_message(1024) and make_message(std::move(a)).
So why constructor Message(const T& data) is duplicate with Message(T&& data) when T = int&?
So why constructor Message(const T& data) is duplicate with Message(T&& data) when T = int&?
Because of reference collapsing rules.
There is no such thing as a reference to reference. When T is an lvalue reference - let's say int &, then syntactically T & appears to be a int & &. But such type does not exist. The rules of the language say that in such case T & collapses into int &.
Similarly, there is no such thing as const reference (not to be confused with reference to const, which people sometimes mean when they say const reference). When T is an lvalue reference - let's say int&, then syntactically T const (same as const T) appears to be a int& const (not to be confused with int const &, which is same as const int &). But such type does not exist. The rules of the language say that in such case T const collapses into int &.
C++11 introduced rvalue references, which brings us new collapsing rules. In short, "rvalue reference to rvalue reference" collpses into rvalue reference, but "lvalue reference to any reference", as well as "any reference to lvalue reference" collapse into lvalue reference. This rule is part of the magic behind how forwarding references work.
As such, given T = int &, these declare the same function:
explicit Message(T&& data) // type of argument is int &
explicit Message(const T& data) // type of argument is int &
Bonus: There are also collapsing rules for non-references: There is no such thing as const const type. As such, given const T where T is const int, then it collapses into const int. Same goes for volatile.

Non-const pointer prefers const T& overload to const T*

Suppose I have two overloads of a function
template <typename T>
void f(const T&) {
cout << "f(T&)" << endl;
}
template <typename T>
void f(const T*) {
cout << "f(T*)" << endl;
}
Why does f(new int) resolves to the f(const T&) instead of f(const T*)? Anywhere in the standard talks about this counter-intuitive behavior?
http://ideone.com/kl8NxL
For overload resolution with template deduction, the first step is to resolve the templates. Then non-template ordering is applied to the results. In your code the template resolutions are:
void f(int * const &) // 1
void f(int const *) // 2
According to C++14 [over.ics.ref], a reference binding directly to an argument as in (1) is an identity conversion (even if there are added cv-qualifiers). The binding of T to T const & is a direct binding, i.e. no temporaries are created and bound.
However, (2) involves a qualification conversion. The argument type int * must be converted to const int * before it matches the function parameter.
The identity conversion is considered a sub-sequence of any non-identity conversion sequence, so (1) wins according to the sub-sequence rule [over.ics.rank]/3.1.1

Why does template parameter deduction for T 'skips' the constness of array elements when function parameter is const reference to T?

Let's consider those definitions:
/*** full type information with typeid ***/
template <class> class Type{};
template <class T> std::string typeStr()
{ return typeid(Type<T>).name(); }
/*** function template for parameter deduction ***/
template <class T> void func(const T &a)
{
std::cout << "Deduced type for T is: " << typeStr<T>() << std::endl;
std::cout << "\targument type is: " << typeStr<decltype(a)>() << std::endl;
}
with pointers to const
If the following statements are executed:
const int i=5, *ip=&i;
func(ip);
The output is:
Deduced type for T is: 4TypeI**PKi**E
So T is actually deduced as a pointer to a constant integer. The fact that the argument is a reference-to-const does not change the deduction, which is what one would expect because the constness of the pointer is low-level.
but with array of const
Nonetheless, if following statements are executed:
const int ia[3] = {3, 2, 1};
func(ia);
The output is:
Deduced type for T is: 4TypeI**A3_i**E
So T is actually deduced as an array of 3 non-const integers. The fact that the argument is a reference-to-const does change the deduction, as if the const was slipping into the array elements.
Actually, versions of CL up to 18 were deducing T as array of 3 const integers was what I expected to be standard, but it seems that since v19 it converged to what GCC and Clang are doing (i.e., deducing as non-const).
Thus, I assume the later behaviour to be standard, but was is the rationale ? It could seem surprising that it does not behave like with pointers.
Edit: Following dip comment, I will report here pointers to CWG issues related to this behaviour, pointers he actually posted as a comment on this answer (answer that actually raised this new question... C++ feels like a deep tunnel)
CWG 1059
CWG 1610
CWG 112
Using this function template prototype:
template <typename T> void func(const T& a);
In your first example, the type deduction works as:
const int* ip;
func(ip) => func<const int*>(const (const int*)& a)
^^^^^^^^^^ ^^^^^^^^^^
Note: This is pseudocode. The full type is const int* const&.
Note that the const int remains const int, but the * becomes * const.
This is because const int* is just a regular, mutable, non-volatile pointer. It is just a *. What it points to is irrelevant.
But in the second example, you have:
const int ia[3];
func(ia) => func<int[3]>(const (int[3])& a)
^^^^^^ ^^^^^^
Note: This is pseudocode. The real type would be const int (&a)[3].
So the type deduction is working the same in both cases, discarding the outer const.
It so happens that a const array is the same as an array of const elements.
It might help to write types like this:
template <typename T> func(T const & a);
int const * ip;
func(ip) => func<int const *>(int const * const & a)
int const ia [3];
func(ia) => func<int [3]>(int const (& a) [3])
On that second example, the const appears to "move" from being applied on the array to being applied on the elements. This is because you can't really have a const array, only an array of const elements.

How to filter const types and non const types using meta programing?

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.

Overloading reference vs const reference

I have the following code:
#include <iostream>
template <typename T>
void f(T& x)
{
std::cout << "f(T& )" << std::endl;
}
template <typename T>
void f(const T& x)
{
std::cout << "f(const T& )" << std::endl;
}
int main()
{
int a = 0;
const float b = 1.1;
f(a); // call f(T&)
f(b); // call f(const T&)
}
The output is:
f(T& )
f(const T& )
My question is: how does the compiler know which function to call? If I remove the references from the function definitions then I get an "ambiguous call" type of error, i.e. error: redefinition of 'f'. For me it looks like f(T&) can be equally well used for both calls, why is the const version unambiguously called for f(b)?
Given two competing overloads, the standard requires the compiler to select the overload that has the "best fit". (If there's no unique best overload, or if the unique best overload is inaccessible, the program is ill-formed.)
In this case, the rules are provided by §13.3.3.2 [over.ics.rank]/p3:
Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if:
[...]
S1 and S2 are reference bindings (8.5.3), and the types to which the references refer are the same type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers is more cv-qualified than the type to which the reference initialized by S1 refers.
This is the example given in the standard:
int f(const int &);
int f(int &);
int g(const int &);
int g(int);
int i;
int j = f(i); // calls f(int &)
int k = g(i); // ambiguous
In your case, const T& is more cv-qualified than T&, so by the standard, f(T&) is a better fit than f(const T&) and is selected by overload resolution.
f(T&) vs. f(T const&)
The two functions are different, in that the first signature states that any variable passed by reference may be modified by the function. So the const float cannot be passed to the first function, and the second is the only viable choice for the compiler. A nonconst variable could be passed to both, so the compiler has to chose the better fit, if there is one. The standard says, that in order to call the second function, the compiler would have to add a const to any nonconst variable, while for the first function this is not necessary. Adding const is an implicit conversion, and it is a "worse" converison (read that as more conversion steps) than adding nothing. Therefore the standard demands that the compiler picks the first function when passing nonconst variables.
In case you wonder: literals and temporaries can not be bound to nonconst references, so f(4), f("meow") and f(someFunc()) will all call the second function.
f(T) vs. f(const T)
They look different, but aren't in terms of overload resolution or function signature. Both of them are call by value, or for the compiler: pass a copy of the argument into the function. The only difference is in a function definition, that you require the variable to be constant in the function body. Any function declaration does not affect the variable definition in the function definition's signature:
void f(int); //a declaration
void f(int i); //redeclaration of the same function
void f(int const); //still the same function redeclared
void f(int const i2); //yes... a redeclaration
void f(int const i) { //at last a function definition and the copy of the argument used in the function body is required to be const
//...
}
void f(int i) { //there is only one f, so this is a redefinition!
//...
}
This is not an "ambuguos call type error", because for the compiler there is only one function and no ambiguity. The error is simply that you did defin the same funciton twice. For that reason, it is preferred in many style guides that function declarations have no top-level const, and compilers will often ignore them and not mention them in error or warning messages.