C++ type qualifiers and equality - c++

Are int& and int the same type? if I use is_same<int,int&>::value i get false but typeid(int).name() == typeid(int&).name() are the same?
secondly the same question for int and const int?
Thirdly int and int*?
I can understand if int and int* are not as one actually stores the address of another object and works differently but I would have thought int& and int are as one is just an alias for another.
Keen to get some good commentary on this.

From Paragraph 5.2.7/4 of the C++11 Standard:
When typeid is applied to a type-id, the result refers to a std::type_info object representing the type of the type-id. If the type of the type-id is a reference to a possibly cv-qualified type, the result of the typeid expression refers to a std::type_info object representing the cv-unqualified referenced type. If the type of the type-id is a class type or a reference to a class type, the class shall be completely-defined.
Thus, typeid(int) and typeid(int&) will give the same result, although the two types are definitely different. Similarly, for the type system int and int const are different types, but the typeid operator ignores the const qualification. From Paragraph 5.2.7/5 of the C++11 Standard:
The top-level cv-qualifiers of the glvalue expression or the type-id that is the operand of typeid are always ignored.
Finally, int and int* are again different types for the type system, and the typeid operator returns different results for them.

Type qualifiers, (const and volatile), create different types. int is a different type from const int.
So do references, pointers, and arrays. For example:
int, int&, int[10] and int* are all different types.
T is a different type from std::remove_reference<T>::type if T is a reference.
The <typeinfo> output of typeid(int).name() is platform-dependent and doesn't have to distinguish between reference/non-reference types. However, the C++ type system definitely distinguishes between T and T&, as you've discovered through type_traits.

std::type_info::name says nothing about identity. If you insist on using typeid to test for identity, try the following:
assert(typeid(T) != typeid(U));
This is using the defined equality comparison operator on the type_info objects. But prepare for disappointment: the above assertion will fail for T = int and U = int& because of §5.2.7/4 (see Andy’s anser).

Related

Is casting to const implied when casting to a narrower const location?

I am trying to word this as best as possible, but an example is a good way to demonstrate my question. Consider the following scenario where variable long a goes into a narrower array element - essentially const int b[0]:
long a = 584;
const int b[4] = {(const int) a, 0, 0, 0};
Is the following snippet equivalent considering that the const isn't explicitly defined:
long a = 584;
const int b[4] = {(int) a, 0, 0, 0};
Both compile, but does the standard define this scenario and outcomes?
Casting to const int produces a value of type int. There are no cv-qualified prvalues of non-class type. See [expr.cast]/1:
The result of the expression (T) cast-expression is of type T. The result is an lvalue if T is an lvalue reference
type or an rvalue reference to function type and an xvalue if T is an rvalue reference to object type; otherwise
the result is a prvalue. [ Note: if T is a non-class type that is cv-qualified, the cv-qualifiers are ignored when
determining the type of the resulting prvalue; see 3.10. — end note ]
and [basic.lval]/4:
Class prvalues can have cv-qualified types; non-class prvalues always have cv-unqualified types. Unless
otherwise indicated (5.2.2), prvalues shall always have complete types or the void type; in addition to these
types, glvalues can also have incomplete types.
So even though you write a cast to const int, the resulting value will have type int.
However, a language lawyer might ask whether the (int) cast and the (const int) cast are guaranteed to produce the same value. Obviously in your case 584 fits into int so the value is guaranteed to be 584. In the general case where the long value might not fit into an int, the last bullet point of [dcl.init]/16 guarantees that the result of casting to const int will still be the same as casting to int:
... Otherwise, the initial value of the object being initialized is the (possibly converted) value of the ini-
tializer expression. Standard conversions (Clause 4) will be used, if necessary, to convert the initializer
expression to the cv-unqualified version of the destination type;
(All wording is from the C++14 standard; emphasis is mine.)
No, const is not implicitly added by the compiler, because it doesn't change anything. Both of your snippets are equivalent.
I don't think the standard defines this scenario, because it's a bit contrived.
Your question is equivalent whether it matters whether a is const here or not (in the example bellow). The answer is no, it doesn't, because you are copying a. It doesn't matter that you can't write to a, because you are only doing a read, not a write.
/*const*/ int a = 10;
const int b = a;

Why are the typeids of int, int&, and int&& same? [duplicate]

cout << typeid(int&).name();
This, in my opinion, should return int& as a type, not an int, but on GCC 4.5.1 and on VS2010 SP1 beta it returns int. Why is this?
This is how typeid is supposed to work. When you apply typeid to a type-id of a reference type, the type_info object refers to the referenced type.
ISO/IEC 14882:2003, 5.2.8 / 4 [expr.typeid]:
When typeid is applied to a type-id, the result refers to a type_info object representing the type of the type-id. If the type of the type-id is a reference type, the result of the typeid expression refers to a type_info object representing the referenced type. If the type of the type-id is a class type or a reference to a class type, the class shall be completely-defined. Types shall not be defined in the type-id.
Your first mistake is expecting anything useful from std::type_info::name(). From the standard:
§18.5.1/1: "The names, encoding rule, and collating sequence for types are all unspecified and may differ between programs."
§18.5.1/7: "const char* name() const; Returns: an implementation-defined NTBS."
If you want a portable solution for meaningful (through not necessarily consistent) type names, I recommend using Boost.TypeIndex's boost::typeindex::type_id_with_cvr<>().pretty_name() (reference).
The C++ spec does not guarantee that type_info::name actually hands back the name of the type as it appears in the C++ source code; in fact, the spec, §18.5.1/7, only guarantees that the function hand back "an implementation-defined NTBS."
Consequently, there's no reason to assume that using typeid to get the name of a type will actually hand back the name of the type as you'd expect it to.
The reason you're seeing the type of int and not int& is that the definition of typeid says that it ignores references. To quote the spec, §5.2.8/4:
When typeid is applied to a type-id, the result refers to a type_info object representing the type of the type-id. If the type of the type-id is a reference type, the result of the typeid expression refers to a type_info object representing the referenced type.
(My emphasis)
This means that typeid(int&) and typeid(int) are completely identical to one another, hence the output being int and not int& or something related to it.

Scott Meyers on Rvalueness

I watched Scott Meyers's extremely informative video on Universal References, in which I learned most of what I know about Rvalue references, moving, and forwarding. At one point he was talking about rvalueness as opposed to the type of a variable, and he said something to the effect of "rvalueness is independent of type".
I understand that you can have a method like this:
void func(MyType&& rRef)
{
// Do domething with rRef...
}
and that here rRef is an lvalue because it can be identified, its address can be taken, etc., even though its type is MyType&&.
But an rvalue cannot be any type, can it? I mean, it can only be a MyType&&, right? In that sense I thought type is not entirely independent of rvalueness. Maybe I'm missing something.
Updated: My point can be made clearer like this. If in func() I call one of two overloaded functions defined as
void gunc(MyType&& rRef)
{
// ...
}
void gunc(MyType& lRef)
{
// ...
}
i.e. either by calling gunc(std::move(rRef)) or gunc(rRef), it seems that the type of the resulting expression between parenthesis is not independent of rvalueness.
The type of an expression does not have any traces of references. So if for a moment we assume that references could have reference type, then we would have the following
int a = 0;
int &ra = a;
int c = a + 42;
int d = ra + 42;
In the above, the expression a would have type int, and the expression ra would have type int&. I think that in nearly all the rules of the spec that relate expressions to type, for example rules that say "expression E must be of type X", we would have to add "... or reference to type X" (think about cast operators). So my educated guess is that this would be too much of a burden to be useful.
C++ has the following types
static type of an expression
Just called "type of the expression" (if not otherwise specified that the dynamic one is meant). This is a property of expressions that designate the type of expressions abstracted away of what the expression refers to at compile time. For example if a refers to an int& or int variable, or is a literal 0, all those expression have type int.
dynamic type of an lvalue expression
This is the type that the non-base-class object that an lvalue expression refers to has.
ofstream fs("/log");
ostream &os = fs;
In this, os has the static type ostream and the dynamic type ofstream.
type of an object or reference
This is the type that an object or reference actually has. An object always has a single type and its type never changes. But what object exists at what location is something only known at runtime, so generally, "type of an object" is a runtime thing too
ostream *os;
if(file)
os = new ofstream("/log");
else
os = new ostringstream;
The type of the object denoted by *os (and the dynamic type of the lvalue *os aswell) is known only at runtime
int *p = new int[rand() % 5 + 1];
Here, the type of the array that was created by operator new is only known at runtime too, and (thanksfully) does and can not escape to the static C++ type system. The infamous aliasing rule (that forbids reading objects from incompatible lvalues, roughly speaking) speaks of "dynamic type" of objects, presumably because it wants to highlight that runtime concerns are of interests. But strictly speaking, saying "dynamic type" of an object is weird, because an object doesn't have a "static type".
declared type of a variable or member
This is the type that you gave in a declaration. In relation to the type of an object or the type of an expression, this sometimes can be subtly different
struct A {
A() { }
int a;
};
const A *a = new const A;
volatile const A *va = a;
Here, the expression a->a has type const int, but the declared type of the member that a->a resolves to has type int (the member entity). The type of the object denoted by a->a has type const int, because we created a const A object with the new expression, and therefore all non-static data members are implicitly const subobjects. In va->a, the expression has type volatile const int a, and the declared type of the variable still has type int and the type of the object referred to still has type const int.
When you say "type of a" and you declared "a" as "int &a;" you therefor always have to say what you mean by "type of a". Do you mean the expression? Then "a" has type int. It can even become nastier. Imagine "int a[10];". Here the expression "a" has type int* or int[10] depending on whether you consider the array to pointer conversion to have taken place in your expression or not, when you ask for the "type of a". If you ask for the type of the variable referred to by "a", then the answer uniquely is int and int[10] respectively.
So what type can an rvalue be of? Rvalues are expressions.
int &&x = 0;
int y = std::move(x);
int z = x;
Here, we have rvalues 0, and std::move(x). Both rvalues have type int. The expression x appearing in the initializer for z is an lvalue, even though it refers to the same object that the rvalue std::move(x) refers to.
Your last point in your question about the overloaded function called with an rvalue or lvalue respectively is interesting. Just because rvalue references are written as int && does not mean that rvalues have type int. They are called rvalue references because you can initialize them with rvalues and the language prefers that initialization over initializing an lvalue reference with an rvalue.
Also, it may be useful to see expressions in name-form that are rvalues
enum A { X };
template<int Y> struct B { };
If you use X or Y, they are rvalues. But those cases are the only one that I can think of.
I think you are leaving off part of his quote:
A final point is worth bearing in mind: the lvalueness or rvalueness
of an expression is independent of its type.
He explains it here. His main point was this:
The type of an expression does not tell you whether it is an lvalue or
an rvalue.
And in his concluding remarks:
In a type declaration, “&&” indicates either an rvalue reference or a
universal reference – a reference that may resolve to either an lvalue
reference or an rvalue reference. Universal references always have the
form T&& for some deduced type T.
Reference collapsing is the mechanism that leads to universal
references (which are really just rvalue references in situations
where reference-collapsing takes place) sometimes resolving to lvalue
references and sometimes to rvalue references. It occurs in specified
contexts where references to references may arise during compilation.
Those contexts are template type deduction, auto type deduction,
typedef formation and use, and decltype expressions.
The type T is used here to mean any type. It could be int&&, or double&&, or Widget&& - it doesn't matter.
First, let's limit the discussion to plain rvalue references and leave universal references aside. We're not talking about template <typename T> ... T &&var ...
As far as regular Type &&var = somevalue; case of rvalue reference, I think the meaning of it is this:
Whatever is bound to this reference was "disposable" when it was bound to the reference. It was going out of scope. If you modified it at the moment you bound it, no one would ever know. There were no other references to it at the time it was bound.
This allows us to take some liberties with rvalue references that we cannot take with other kinds of variables. The first use that comes to mind is to steal its contents with swap().

Why aren't template type parameters inferred as 'const'? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
deducing references to const from rvalue arguments
If I have
template<class T>
void foo(T &) { }
and I call it as foo((const int)5), given that the argument is a const int, why doesn't the compiler automatically infer T to be const int?
The type of an integer literal is int, not const int, according to C++03 Standard, clause 2.12.1.2.
The type of an integer literal depends on its form, value, and suffix.
If it is decimal and has no suffix, it has the first of these types in
which its value can be represented: int, long int;...
Update
Another relevant type deduction rule might be 14.8.2.1.2.
If P is not a reference type:
[...]
— 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.
The code provided by OP wouldn't even compile because it's illegal to bind a non-const reference to rvalue.
It does, if it's given a const type. Rvalues (prvalues in C++11) with
non-class types, however, are never cv-qualified, even if you try to say
they are: the expression ((const int)5) has type int. The reasoning
here is that cv-qualifications only apply to objects, and temporaries of
non-class types aren't objects, but pure values; cv-qualifications can't
apply, because there's nothing to be const or volatile.
If you write:
int const i = 42;
foo( i );
, your template will instantiate with T = int const. (As you
wrote it, the code shouldn't compile, because the deduced type
is int, so the function takes an int&, which can't be
initialized with an rvalue.)

typeid doesn't return correct type

cout << typeid(int&).name();
This, in my opinion, should return int& as a type, not an int, but on GCC 4.5.1 and on VS2010 SP1 beta it returns int. Why is this?
This is how typeid is supposed to work. When you apply typeid to a type-id of a reference type, the type_info object refers to the referenced type.
ISO/IEC 14882:2003, 5.2.8 / 4 [expr.typeid]:
When typeid is applied to a type-id, the result refers to a type_info object representing the type of the type-id. If the type of the type-id is a reference type, the result of the typeid expression refers to a type_info object representing the referenced type. If the type of the type-id is a class type or a reference to a class type, the class shall be completely-defined. Types shall not be defined in the type-id.
Your first mistake is expecting anything useful from std::type_info::name(). From the standard:
§18.5.1/1: "The names, encoding rule, and collating sequence for types are all unspecified and may differ between programs."
§18.5.1/7: "const char* name() const; Returns: an implementation-defined NTBS."
If you want a portable solution for meaningful (through not necessarily consistent) type names, I recommend using Boost.TypeIndex's boost::typeindex::type_id_with_cvr<>().pretty_name() (reference).
The C++ spec does not guarantee that type_info::name actually hands back the name of the type as it appears in the C++ source code; in fact, the spec, §18.5.1/7, only guarantees that the function hand back "an implementation-defined NTBS."
Consequently, there's no reason to assume that using typeid to get the name of a type will actually hand back the name of the type as you'd expect it to.
The reason you're seeing the type of int and not int& is that the definition of typeid says that it ignores references. To quote the spec, §5.2.8/4:
When typeid is applied to a type-id, the result refers to a type_info object representing the type of the type-id. If the type of the type-id is a reference type, the result of the typeid expression refers to a type_info object representing the referenced type.
(My emphasis)
This means that typeid(int&) and typeid(int) are completely identical to one another, hence the output being int and not int& or something related to it.