Why does std::is_const<const int&>::value evaluate to false? - c++

This is a spin off of the question How to check if object is const or not?.
I was surprised to see the following program
#include <iostream>
#include <type_traits>
int main()
{
std::cout << std::boolalpha;
std::cout << std::is_const<const int&>::value << "\n";
}
produced this output
false
In what context does it make sense to think of const int& as a non-const type?

Perhaps it'll be easier to understand with this example
std::cout << std::is_const<int const *>::value << "\n"; // pointer to const int
std::cout << std::is_const<int * const>::value << "\n"; // const pointer to int
Output:
false
true
The first type is a pointer to a const int, while in the second the int * itself is const. Hence it results in true while the former is false. Similarly, what you have a reference to a const int. If int& const were valid it'd result in true.

A const qualifier on a reference just means that the value can't be modified via the reference. It can still be modified by other means. For example:
int a = 1;
const int &b = a;
std::cout << b << std::endl; // Prints 1
a = 2;
std::cout << b << std::endl; // Prints 2
Thus, you can't assume that the value of a const reference is actually constant.

Related

Types should be the same but are not

I am not sure I understand why the first test evaluates to true and the second to false. I know that the information from typeid().name() is usually not reliable, but my main problem is with the typeid itself. I don't understand why the type of *test is not Location<1>, or what else is wrong. Any thoughts? Is there same wrapper around a type here that I don't see? Thanks in advance, and apologies if the answer is obvious.
#include <iostream>
#include <utility>
#include <typeinfo>
class LocationAbstract
{
virtual void get_() = 0;
};
template<int i>
class Location : public LocationAbstract
{
public:
static constexpr int test = i;
virtual void get_() override
{
return;
}
};
template <int i>
Location<i> LocationGenerator()
{
Location<i> test{};
return test;
}
int main()
{
LocationAbstract *table[10];
table[0] = new decltype(LocationGenerator<0>());
table[1] = new decltype(LocationGenerator<1>());
Location<1> *test;
try
{
std::cout << "Casting\n";
test = dynamic_cast<Location<1>*>(table[1]);
}
catch (std::bad_cast &e)
{
std::cout << "Bad cast\n";
}
// test1, evaluates to true
std::cout << (typeid(*test) == typeid(*dynamic_cast<Location<1>*>(table[1]))) << "\n";
std::cout << typeid(*test).name() << "\n";
std::cout << typeid(*dynamic_cast<Location<1>*>(table[1])).name() << "\n----\n";
// test2, why does this evaluate to false while the above evaluates to true ?
std::cout << (typeid(Location<1>()) == typeid(*dynamic_cast<Location<1>*>(table[1]))) << "\n";
std::cout << typeid((Location<1>())).name() << "\n";
std::cout << typeid(*dynamic_cast<Location<1>*>(table[1])).name() << "\n";
auto test1 = Location<1>();
auto test2 = *dynamic_cast<Location<1>*>(table[1]);
std::cout << typeid(test1).name() << " and " << typeid(test2).name() << "\n";
return 0;
}
An extra set of () makes all the difference here. In typeid(Location<1>()) and typeid((Location<1>())), Location<1>() actually means two totally different things.
In typeid(Location<1>()), Location<1>() is interpreted as a function type that returns a Location<1> and takes no parameters.
In typeid((Location<1>())), Location<1>() is interpreted as value-initializing an anonymous Location<1> object.
The typeid operator can work on either types or expressions. That is, you can say typeid(int) as well as typeid(42). Since Location<1>() can be interpreted as a type, the language does so. (Location<1>()) cannot be interpreted as a type though, so it must be interpreted as an expression. The only thing Location<1>() can mean as part of an expression is to value-initialize an anonymous Location<1> object, so typeid gives you the type of that object.
Let this be yet another reason to prefer uniform-initialization syntax when creating temporary objects; Location<1>{} would not have this ambiguity.
Examine these two lines:
std::cout << (typeid(Location<1>()) == typeid(*dynamic_cast<Location<1>*>(table[1]))) << "\n";
std::cout << typeid((Location<1>())).name() << "\n";
In the first line, you use typeid(Location<1>()). typeid can take types as well as expressions, and Location<1>() is a function type with no parameters and a return type of Location<1>.
So why does the name print the same? That's because of the second line: typeid((Location<1>())). By wrapping the argument in parentheses, it is no longer a valid type, so it is treated as an expression and the name of typeid(Location<1>) is printed. Removing the extra parentheses prints F8LocationILi1EEvE under the same mangling scheme.
To avoid the ambiguity, you can also use the type directly (typeid(Location<1>)) or use braces: typeid(Location<1>{})).

What is the point of returning a const alias from a function

Please look at the following code and help me understand:
Why the functionality to return a const alias to a literal like my f2 function exists. I don't understand what the point is.
The difference between f2 and f3 is that const does allow me to put a literal in the return statement, but again why?
Any help in understanding this is appreciated.
#include <iostream>
const int f1(int a)
{
return 15;
}
const int& f2(int a)
{
return 14;
}
int& f3(int a)
{
a = 12;
return a;
}
int main()
{
auto a{ 10 };
auto b = f1(a);
auto c = f2(a);
auto d = f3(a);
std::cout << a << " " << b << " " << c << " " << d << std::endl;
a = 1;
b = 2;
c = 3;
d = 4;
std::cout << a << " " << b << " " << c << " " << d << std::endl;
}
Both f2 and f3 have undefined behaviour. You are returning references to local variables. Those local variables are destroyed when the function ends, and the reference is dangling.
The difference between a const reference, and a non-const reference is that a const reference can bind to both rvalues and lvalues.
For non-const references you have to distinguish between lvalue-reference(int&) and rvalue-reference(int&&).
So using the function signature int&& f2(int a) would also compile, but equally have undefined behaviour.
The main reason this is usefull is because when we pass a reference to a function, the function signature tell us if we are expecting an lvalue or an rvalue. We can also overload both and decide to move/copy depending on what we get.
In the case where we don't care, or if we only want to read from the value we can use a const reference and be able to accept both lvalues and rvalues that are passed in.
void foo(MyClass& mc) {
// We know mc is an lvalue.
// We could copy mc, or modify it if we want to use it as an output parameter.
}
void foo(MyClass&& mc) {
// We know mc is an rvalue.
// We know it would be safe to move from mc in this case.
}
MyClass mc;
foo(mc); // Callsthe first overload
foo(MyClass{}); // Calls the second overload
// The two functions above can be overloaded, so we can make sure we deal
// with both cases in the right way
void foo2(const MyClass& mc) {
// This can be both an rvalue or lvalue.
// We don't really care since the reference
// is const we are only going to read from it.
}
foo2(mc); // Both calls work
foo2(MyClass{});
The b, c and d variables in main are initialized with a copy of what the functions return. No matter if they return a copy, a ref or a const ref.
To keep the attributes of the returned value, let's change the first lines in main:
int main()
{
auto a{ 10 };
auto& b = f1(a); // Does not compile, a ref can't be tied to a r-value
auto& c = f2(a); // Ok, c's type is 'const int&'
auto& d = f3(a); // Ok, d's type is 'int&'
std::cout << a << " " << b << " " << c << " " << d << std::endl;
a = 1;
b = 2;
c = 3; // Does not compile. c is a ref to a const
d = 4;
std::cout << a << " " << b << " " << c << " " << d << std::endl;
}
So, the point is you can return a reference to an internal variable, but now allowing the caller to change it. Doing so (instead of returning a copy), you
avoid the copy
allow the caller to see any later change
Not much sense for the code above, but think of a method inside a class, where the internal variable can be changed in other ways.
Besides the return type, f2 and f3 are not correct, as they return a reference to a not-in-memory (f2) or temporary object (f3).
Let's say you write a function that needs to return a complex object, but this object shouldn't be modified (such as pointer to a shared resource, class-property, some sort a singleton data and so on).
For the sake of this answer, lets assume the type in "struct Point".
You have 2 options to do so:
return it by value, which will create a deep copy of its primitive type members and a shallow copy of its by-reference-types members:
const struct Point f2(...)
return it by reference, which will copy only the pointer to the object:
const struct Point* f2()
const struct Point& f2()
both are valid, while the second one has the advantage when dealing with heavy objects.
In the code you provided you do not see the difference because "int" is a primitive type which means it has known way to be copied. This means var "c" isn't actually an alias nor a const, its an int who took its value from the return type of f2

Rvalue and lvalue references as member variable in a class - Valid?

#include <iostream>
#include <string>
#include <vector>
class X
{
public:
int& a;
int&& b;
X(int& c, int && d):a(c), b(std::move(d))
{
}
};
X* x = nullptr;
void fun()
{
std::cout<<"Fun\n";
int l = 8;
int r = 9;
std::cout << &(l) << std::endl;
std::cout << &(r) << std::endl;
x = new X(l, std::move(r));
}
int main()
{
fun();
std::cout << x->a << std::endl;
std::cout << &(x->a) << std::endl;
std::cout << x->b << std::endl;
std::cout << &(x->b) << std::endl;
}
=> Will values of member variable references (lvalue and rvalue) be garbage?
I am seeing different behavior across different compilers. So wanted to know what c++ standard says about this.
You're binding reference members to local variables, which will be destroyed when get out of the function fun(). After that, both references become dangled, any dereference on them leads to UB.
This is true for both lvalue and rvalue reference members.

Clarification wanted re. C++ type_traits

It appears that to test for const-ness, one must test the template-parameter, but to test for rvalue-ness, one must test an actual parameter. (This is using VC++ 2012.) This code illustrates what I mean:
#include <type_traits>
#include <string>
#include <iostream>
using namespace std;
template<class T>
void f(T& x) {
cout << "f() is_const<T> and is_const<decltype<x)>" << endl;
cout << is_const<T>::value << endl; // Prints 1 when arg is const
cout << is_const<decltype(x)>::value << endl; // Prints 0 when arg is const
}
template<class T>
void g(T&& x) {
cout << "g() is_const<T> and is_const<decltype<x)>" << endl;
cout << is_const<T>::value << endl; // Prints 0 when arg is const
cout << is_const<decltype(x)>::value << endl; // Prints 0 when arg is cons
cout << "g() is_rvalue_reference<T> and is_rvalue_reverence<decltype(x)>" <<endl;
cout << is_rvalue_reference<T>::value << endl; // Prints 0 when arg is rvlaue
cout << is_rvalue_reference<decltype(x)>::value << endl; // Prints 1 when arg is rvalue
}
int main()
{
const std::string str;
f(str); // const argument
cout << endl;
g(std::string("")); // rvalue argument
return 0;
}
I am struggling to understand why that is. Can someone explain, or point me to an article that explains it? If need be, I will dig into the C++11 standard. Anyone know the pertinent sections?
The reason is that you're misunderstanding things. x will never be const in any of those examples, simply because there are no const reference types (you can't change what a reference refers to anyways). In is_const<T> you're basically ignoring that you declared x as T&.
A similar misunderstanding is at work for the rvalue ref test. The T in T&& (which is called a universal reference, btw) will be deduced as U& when you pass an lvalue and as U when you pass an rvalue. When testing is_rvalue_reference<T>, you're ignoring again that you declared x as T&&. When testing is_const<T>, you didn't account for the fact that T will be a reference, which, as said above, can never be const.
The correct tests for g would be
std::is_const<typename std::remove_reference<T>::type>::value and
std::is_rvalue_reference<T&&>::value

default argument mismatch in C++?

Consider the following code:
#include <iostream>
class Bar
{
public:
void foo(bool b = false, std::string name = "");
};
void Bar::foo(bool b, std::string name)
{
if (!b)
{
std::cout << "b is false" << std::endl;
}
else
{
std::cout << "b is true" << std::endl;
}
}
int main()
{
Bar myBar;
myBar.foo("bla");
return 0;
}
I guess C++ is not broken, but can anyone please explain why the output is true? I am working on VS 2010 but I also checked in ideone which runs gcc
The compiler is implicitly casting the first parameter, a char const[4], to bool, and results in true.
It's equivalent to
myBar.foo((bool)"bla");
which is also equivalent to
myBar.foo((bool)"bla", "");
Because the "bla" is a char const[4], which decays to const char*, and is cast to a bool. Since it's value is not 0, the cast takes the value true. A simpler example:
#include <iostream>
int main()
{
std::cout << std::boolalpha; // print bools nicely
bool b = "Hello";
std::cout << b << "\n";
}
produces
true
Bool parameter convert "bla" to true.
You need to change order of your parameters.