template <typename T>
void test(const T& x) {}
int a {};
int& ref = a;
const int& c_ref = a;
test(c_ref) // T = int, x = const int&
test<int&>(ref); // T = int& , x = int&
Why does the function template parameter x loses the const qualifier?
In the explicit (non-deduced) instantiation
test<int&>(ref);
this is the (theoretical) signature you get
void test<int&>(const (int&)& x)
which shows that the const-qualification applies to the whole (int&), and not only the int. const applies to what is left, and if there's nothing, it applies to what is right: int&, but as a whole - there, it applies to &, again because const applies to what's on its left. But there are no const references (they aren't changeable at all, i.e., they can't rebind), the const is dropped, and reference collapsing rules contract the two & into one.
Related
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.
template <typename T>
void f(T t)
{}
int x = 1;
const int & rx = x;
const int * px = &x;
f(rx); // t is int
f(px); // t is const int *, instead of int *, WHY???
I'm confused now. According to Effective Modern c++,
It’s important to recognize that const is ignored only for by-value
parameters. As we’ve seen, for parameters that are references-to- or
pointers-to-const, the constness of expr is preserved during type
deduction.
I think it means
template <typename T>
void f(T * t)
{}
f(px); // t is const int *
template <typename T>
void f(T & t)
{}
f(cx); // t is const int &
template <typename T>
void f(T t)
{}
f(value); // const or volatile of value will be ignored when the type of the parameter t is deduced
So I think when f(px) above, t should be int *, but in fact it is const int *.
Why the const of reference is ignored but the const of pointer isn't?
Or, why isn't rx const int &?
So I think when f(px) above, px should be int *, but in fact it is const int *.
The point is the type of parameter, behaviors change for passed-by-value and passed-by-reference/pointer.
When passed-by-value, the constness of the argument itself is ignored. For a pointer parameter, the constness of pointer is ignored (const pointer -> pointer), but the constness of pointee is still preserved (pointer to const -> pointer to const).
It does make sense because when pass a pointer, the pointer itself is copied, but the pointee is the same, they're both pointing to the same thing, so constness of pointee is preserved; from the point of callers, they won't want the object to be modified, they might use it later. While pass a reference (reference doesn't matter in fact here), you'll get a brand new value copied, which has nothing to do with the original one, then constness is ignored.
As the following explanations in the book, when const pointer to const (a const char* const) passed,
template<typename T>
void f(T param); // param is still passed by value
const char* const ptr = // ptr is const pointer to const object
"Fun with pointers";
f(ptr); // pass arg of type const char * const
The type deduced for param will be const char*.
Only top-level const/volatile qualifiers are ignored. All others are inherent qualities of your type. In other words, you're copying a pointer - it means the function operates on a copy and any modifications to it (like assigning another variable's address) do not modify the original pointer. But if you pass a pointer to const int, having the function modify the integer is very much counter-intuitive.
template <typename T>
void f(T t)
{
t = &another_variable; // ok
}
f(px);
and
void f(T t)
{
*t = 42; // not ok!
}
f(px); // secretly modifying `x` through a pointer to const...
Reference: this answer for pointer to const vs. const pointer differences
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.
Consider something like:
template <typename T>
void f(T& x)
{
....
}
Why does something like const int binds to f(T&)?
This seems to me kind of a violation of const-correctness. In fact, if f() takes a non-const T& reference, then it's very likely that f() will modify its argument (else, f() would have been defined as void f(const T&)).
In code like this:
template <typename T>
inline void f(T& x)
{
x = 0;
}
int main()
{
int n = 2;
f(n);
const int cn = 10;
f(cn);
}
the compiler tries to call f() with T = const int, then of course there is an error message because of the x = 0; assignment inside f()'s body.
This is the error message from GCC:
test.cpp: In instantiation of 'void f(T&) [with T = const int]':
test.cpp:13:9: required from here
test.cpp:4:7: error: assignment of read-only reference 'x'
x = 0;
^
But why does the compiler try to bind a const argument with a function template which takes a non-const parameter?
What's the rationale behind this C++ template rule?
T binds to const int.
To avoid that, you may use SFINAE:
template<typename T>
typename std::enable_if<!std::is_const<T>::value, void>::type
f(T& arg) {}
or deleted function:
template <typename T> void f(T& arg) {}
template <typename T> void f(const T&) = delete;
You can use std::enable_if plus e.g. std::is_const to avoid that T binds to a const type.
Re …
“What's the rationale behind this C++ template rule?”
it can possibly be found in Bjarne's design-and-evolution book, but about the most common rationale is that the rules have been chosen for simplicity and uniformity, and so it appears to be also here: treating some types in special ways would introduce needless complexity.
const int cn = 10;
That means 'cn' is const, you can't change it in anyway, anywhere and anytime.
More:
const int cia = 10;
int ia = 10;
The type of cia is different with ia. So T will be const int, not int.
typedef const int cint;
cint cia = 10;
int ia = 10;
T will be used as cint, not int.
I have been struggling with this one for about a half day and it seem that at least XCode 4.6 has a bug where certain declaration of template class would violate the language and allow to pass const data from within class to external functions with parameters delcared as without const modifiers.
The example below WILL COMPILE, even tough template declaration of
Tcaller::call() method specifies const arguments passed as reference
but static cmp function is providing useless *const & modifiers.
template< typename T> struct Tcalled
{
// !!!error - this prototype doesn't protect the data passed to function
// because it should be declared with const modifiers but it wouldn't compile then.
// SEE: Below my NOTE for correct function prototype.
static bool cmp(const Tcalled*& item, const int& key) //<- correct but doesn't work
static bool cmp(Tcalled* const & item, const int& key) //<- invalid but works!!
{
return (item->index = key); /// error - we modify const object here !
}
T index;
};
template < typename T> struct Tcaller
{
Tcaller(){}
template < typename K, bool (*compare)(const T& item, const K& key) >
bool call(int k) const { return compare(data, k); }
T data;
};
int main(int argc, char *argv[])
{
const Tcaller<Tcalled<int>* > tmp; // <- const data
int k = 1;
tmp.call<int,Tcalled<int>::cmp>(k); //call here WILL modify const data !!
}
And the question : HOW I can force XCode to obey the rules and allow me to prototype
my static function as it was declared for template parameter ? As for now these are the errors from XCode I get when I declare my static method correctly :
No matching member function for call to 'call'
Candidate template ignored: invalid explicitly-specified argument for template parameter 'compare'
Thanks!
Presumably you mean that it works when the argument is declared as Tcalled<T>* const & item and the body of cmp should be using == not =.
You have a misunderstanding about the way template arguments are instantiated. It's not just a copy-paste substitution of the template arguments. You're expecting that const T& when instantiated with T as Tcalled<int>* will be equivalent to const Tcalled<int>*&; that is, a "reference to pointer to const Tcalled<int>.
However, this is wrong, the const applies to the whole T type. So really, after instantiation, const T& is equivalent to Tcalled<int>* const&. This is why having the argument declared as Tcalled* const & item works fine.
To get this to work with the declaration as const Tcalled<T>*& item, a number of things have to change:
The call function template arguments should be defined like so:
template < int (*compare)(T& item) >
That is, the function pointer type takes a T&, not a const T&. This makes sense as the cmp function does not take a reference to const object at all (it takes a reference to non-const pointer).
The call function should not be const:
int call() { return compare(data); }
This is because it's passing its member T to compare, which is a reference to non-const object (the pointer itself is not const). It can't do that if it is a const function because it cannot guarantee that compare won't modify the object.
Tcaller must be instantiated with T as const Tcalled<int>*:
Tcaller<const Tcalled<int>* > tmp;