Is temporary object originally const? - c++

Is this code UB?
struct A
{
void nonconst() {}
};
const A& a = A{};
const_cast<A&>(a).nonconst();
In other words, is the (temporary) object originally const? I've looked through the standard but cannot find an answer so would appreciate quotations to relevant sections.
Edit: for those saying A{} is not const, then can you do A{}.nonconst() ?

The initialization of the reference a is given by [dcl.init.ref]/5 (bold mine):
Otherwise, if the initializer expression
is an rvalue (but not a bit-field)[...]
then the value of the initializer expression in the first case and the result of the conversion in the second case is called the converted initializer.
If the converted initializer is a prvalue, its type T4 is adjusted to type “cv1 T4” ([conv.qual]) and the temporary materialization conversion ([conv.rval]) is applied.
So it means that the type prvalue expression that initialize the reference, A{}, is adjusted to const A.
Then [conv.rval] states:
A prvalue of type T can be converted to an xvalue of type T.
This conversion initializes a temporary object ([class.temporary]) of type T.
So the type of the temporary object, bound to the reference is the same as the adjusted prvalue type: const A.
So the code const_cast<A&>(a).nonconst(); is undefined behavior.

The type of a temporary is whatever type you declared it with.
Unfortunately, as Oliv points out in their answer reference initialization rules transform the type to match the reference type so in this case a actually refers to a const A. It is basically doing
using const_A = const A;
const A& a = const_A{};
Because you can actually create constant prvalues if you ever want to stop a overload set from accepting a constant prvalue you need to have
ret_type function_name(some_type const&&) = delete;
otherwise if you have
ret_type function_name(some_type const&)
in the overload set it then the constant prvalue would bind to that if you only deleted
ret_type function_name(some_type&&)
instead. You can see this working with
struct bar{};
void foo(bar const&) { std::cout << "void foo(bar const&)\n"; }
void foo(bar&&) =delete;
using c_bar = const bar;
int main()
{
foo(c_bar{});
}
Here, void foo(bar const&) gets called since c_bar{} is actually const instead of getting a deleted function error if you had used foo(bar{});. Adding
void foo(bar const&&) = delete;
is needed to actually stop foo(c_bar{}); from compiling.

Related

An issue about the member function qualified by volatile qualifier

#include <iostream>
struct A{
A() = default;
A(volatile const A&){}
void show()const volatile {
}
};
int main(){
volatile A a;
//A b = std::move(a); // ill-formed
std::move(a).show(); //OK
}
Consider the example, the results of the example are out of my understanding about some relevant rules.
For A b = std::move(a);, it's ill-formed, because it violates the following rule, that is:
dcl.init.ref#5.2
Otherwise, if the reference is an lvalue reference to a type that is not const-qualified or is volatile-qualified, the program is ill-formed.
That means, a lvalue reference to const volatile-qualified T cannot bind to any rvalue even though they're reference-compatible. A b = std::move(a); obviously violates this rule, hence it's ill-formed.
However I don't know why compile std::move(a).show();without reporting wrong. According to this rule:
For non-static member functions, the type of the implicit object parameter is
“lvalue reference to cv X” for functions declared without a ref-qualifier or with the & ref-qualifier
The type of the implicit object parameter of member function show will be volatile const A& . In general, it definitely violates [dcl.init.ref#5.2]. If change the definition of member function show to:
void show() volatile const& {
}
std::move(a).show(); will be ill-formed. So must be some magic in the following rule that make std::move(a).show(); be compiled before changing show. The rule is:
over.match.funcs#general-5
For non-static member functions declared without a ref-qualifier, an additional rule applies:
even if the implicit object parameter is not const-qualified, an rvalue can be bound to the parameter as long as in all other respects the argument can be converted to the type of the implicit object parameter.
Honestly, I really don't know what does the wording "in all other respects" mean? And what does the "the type of the implicit object parameter" refer to? Does the "type" refer to volatile const A& or the referenced type volatile const A? The wording is very vague. Anyhow, lvalue reference to const volatile T cannot bind to any rvalue of type T. So, how to interpret that?
As a contrast:
#include <iostream>
struct B{
void show(){}
};
int main(){
volatile B b;
std::move(b).show(); //ill-formed
}
the type of the implicit object parameter of show would be B&, According to [over.match.funcs#general-5], even though ignore const-qualifier, it's still ill-formed due to it discards the volatile-qualifier. From this example, it implies that, For this sentence "in all other respects the argument can be converted to the type of the implicit object parameter", where the type should refer to reference type rather than the type the reference refers to. If the magic is this, it's still not sufficient to make std::move(a).show(); to be well-formed.
So, how to interpret these issues? I don't know how to use [over.match.funcs#general-5] to interpret these two examples.
struct A {
A() = default;
A(volatile const A &) {}
void show() const volatile {}
};
int main() {
volatile A a;
std::move(a).show(); // OK
}
The implied object parameter for the member function show() is, as per [over.match.funcs]/4, const volatile A&, such that for the purpose of overload resolution, we may, as per [over.match.funcs]/5, consider the data member function as
void show(const volatile A&);
Now, with this in mind, let's first simplify the example, with the purpose of:
Comparing why an rvalue reference of A seemingly may bind to an implied object parameter or type const volatile A& but not to say a function parameter of the same type when the parameter is for a regular free function.
Thus, consider the following simplified example:
#include <memory>
struct A {
void show() const volatile {}
};
void g(const volatile A &) { }
int main() {
volatile A a;
g(std::move(a)); // (i) Error.
std::move(a).show(); // (ii) OK.
}
The error message at (i), in GCC (10.1.0) is:
error: cannot bind non-const lvalue reference of type const volatile A& to an rvalue of type std::remove_reference<volatile A&>::type
{aka volatile A}
which is expected (as you have noted yourself) as per [dcl.init.ref]/5.2, which disallows rvalues to bind, in initialization, to volatile references, even if they are const-qualified.
Then why is (ii) accepted? Or, conversely, why does the restriction of [dcl.init.ref]/5.2 apparently not apply for the similar case of the implicit object parameter of a member function?
The answer lies in
[over.match.funcs
]/5.1, which contains an exception for member functions declared without a ref-qualifier:
[over.match.funcs
]/5 [...] For non-static member functions declared without a ref-qualifier, an additional rule applies:
/5.1 even if the implicit object parameter is not const-qualified, an rvalue can be bound to the parameter as long as in all other respects the argument can be converted to the type of the implicit object parameter.
[over.match.funcs ]/5.1 removes the prohibation of [dcl.init.ref]/5 regarding rvalues (rvalue binding), and the remaining criteria is applied to whether argument (rvalue ignored; volatile A) can be ("in all other respects") converted to the implicit object parameter (const volatile A&). As the implicit object parameter, as shown above, in this case is always an lvalue reference, "in all other respects" here essentially means that the implicit object parameter is reference-compatible (as per [dcl.init.ref]/4) with the (rvalue ignored) argument type.
// [over.match.funcs ]/5.1 special case: rvalue prohibition waived
volatile A a; // argument: a
const volatile A& aref = a; // ok, reference-compatible
// ^^^^^^^^^^^^^^^^^ implicit object parameter
Arguably [over.match.funcs]/5.1 could be clearer on that it applies both for the case where non-const qualification (usually) prohibits from-rvalue binding, and that volatile-cv-qualification (usually) prohibits from-rvalue binding.
We may finally query the compiler(s) whether this is actually the particular rule which it uses to allow (ii), by explicitly adding the &-ref-qualifier, a change that as per [over.match.funcs]/4.1 will have no effect on the type of the implicit object parameter:
#include <memory>
struct A {
void show() const volatile & {}
};
void g(const volatile A &) { }
int main() {
volatile A a;
g(std::move(a)); // (i) Error.
std::move(a).show(); // (ii') Error.
}
As expected, if we add a &-qualifier to the show() overload, (ii) likewise fails as (i) did, albeit with another error message (GCC):
error: passing std::remove_reference<volatile A&>::type {aka volatile A} as this argument discards qualifiers
For this error, Clang (10.0.0) arguably has a more on-spot error message:
error: this argument to member function show is an rvalue, but function has non-const lvalue ref-qualifier

Are temporaries bound to const references considered const objects? [duplicate]

Is this code UB?
struct A
{
void nonconst() {}
};
const A& a = A{};
const_cast<A&>(a).nonconst();
In other words, is the (temporary) object originally const? I've looked through the standard but cannot find an answer so would appreciate quotations to relevant sections.
Edit: for those saying A{} is not const, then can you do A{}.nonconst() ?
The initialization of the reference a is given by [dcl.init.ref]/5 (bold mine):
Otherwise, if the initializer expression
is an rvalue (but not a bit-field)[...]
then the value of the initializer expression in the first case and the result of the conversion in the second case is called the converted initializer.
If the converted initializer is a prvalue, its type T4 is adjusted to type “cv1 T4” ([conv.qual]) and the temporary materialization conversion ([conv.rval]) is applied.
So it means that the type prvalue expression that initialize the reference, A{}, is adjusted to const A.
Then [conv.rval] states:
A prvalue of type T can be converted to an xvalue of type T.
This conversion initializes a temporary object ([class.temporary]) of type T.
So the type of the temporary object, bound to the reference is the same as the adjusted prvalue type: const A.
So the code const_cast<A&>(a).nonconst(); is undefined behavior.
The type of a temporary is whatever type you declared it with.
Unfortunately, as Oliv points out in their answer reference initialization rules transform the type to match the reference type so in this case a actually refers to a const A. It is basically doing
using const_A = const A;
const A& a = const_A{};
Because you can actually create constant prvalues if you ever want to stop a overload set from accepting a constant prvalue you need to have
ret_type function_name(some_type const&&) = delete;
otherwise if you have
ret_type function_name(some_type const&)
in the overload set it then the constant prvalue would bind to that if you only deleted
ret_type function_name(some_type&&)
instead. You can see this working with
struct bar{};
void foo(bar const&) { std::cout << "void foo(bar const&)\n"; }
void foo(bar&&) =delete;
using c_bar = const bar;
int main()
{
foo(c_bar{});
}
Here, void foo(bar const&) gets called since c_bar{} is actually const instead of getting a deleted function error if you had used foo(bar{});. Adding
void foo(bar const&&) = delete;
is needed to actually stop foo(c_bar{}); from compiling.

Class reference to an object instantiated by a literal

What is happening here?
struct A {
A (int){std::cout<<'A';}
};
const A& a = 3;
My assumption is since the literal 3 is an rvalue, temporary materialization conversion is applied. A temporary object is created by calling the constructor thus printing A and the reference is bound to the resulting object.
You are correct. a is an lvalue reference [dcl.ref]/2 to a const-qualified class type, the initializer expression 3 is a prvalue [expr.prim.literal]/1 of non-class type int [lex.icon]/2 that is neither reference-related nor reference-compatible to const A [dcl.init.ref]/4. Therefore, [dcl.init.ref]/5.4.1 would seem to apply. There is a converting constructor [class.conv.ctor]/1, which will be used to convert 3 to a prvalue of type const A, which is then used to initialize the reference. This time around, the intializer expression now is a prvalue of type const A, which is reference related to const A. Thus, [dcl.init.ref]/5.3 should apply, which will perform temporary materialization [conv.rval]. The lifetime of the temporary object created in the process will be extended [class.temporary]/6 since it is being bound to the reference a which lives in global namespace scope…
implicit conversion was occured. it converted from 3 to an object of A, then reference a point to this object.
better practice is add keyword explicit in constructor which take only one parameter like this. then no implicit conversion happen. then below code will throw error.
refer:
https://www.geeksforgeeks.org/g-fact-35/
https://www.learncpp.com/cpp-tutorial/9-13-converting-constructors-explicit-and-delete/
struct A {
explicit A (int){std::cout<<'A';}
};
const A& a = 3;

Single value in constructs vs argument list for construction

This question pertains to this code snippet reproduced below:
struct A {
A():b(0) { }
A(const A&, int b = 0):b(b) { }
int b;
};
int main() {
A a;
const A& a1 = { a };
const A& a2 = { a, 1 };
a.b = 2;
std::cout << a1.b << a2.b << std::endl;
}
The right hand side of the assignment of a1 can be a single value in constructs or an argument list for construction. Is there anywhere in the standard that specifies which interpretation takes precedence? For a2, it is a construction of a temporary A whose address is assigned to a2, if I did not misunderstand.
BTW, compiling this code by clang++ in Coliru produced output 21. gcc++-4.8 output 01.
The definition of list-initialization has changed quite a bit since the publication of the C++11 Standard due to defect reports.
From draft n3485 (after the Standard has been published, with some corrections but without C++1y features) [dcl.init.list]/3
List-initialization of an object or reference of type T is defined as follows:
If T is an aggregate [...]
Otherwise, if the initializer list has no elements [...]
Otherwise, if T is a specialization of std::initializer_list<E> [...]
Otherwise, if T is a class type [...]
Otherwise, if the initializer list has a single element of type E and either T is not a reference type or its referenced type is reference-related to E, the object or reference is initialized from that element; if a narrowing conversion is required to convert the element to T, the program is ill-formed.
Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is list-initialized, and the reference is bound to that temporary
[...]
In the latest draft from the Committee's github repo (ce016c64dc), only a slight change applies here to one point [dcl.init.list]/3:
Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is copy-list-initialized or direct-list-initialized, depending on the kind of initialization for the reference, and the reference is bound to that temporary.
From draft n3242 (before the Standard) [dcl.init.list]/3:
List-initialization of an object or reference of type T is defined as follows:
If the initializer list has no elements [...]
Otherwise, if T is an aggregate [...]
Otherwise, if T is a specialization of std::initializer_list<E> [...]
Otherwise, if T is a class type [...]
Otherwise, if T is a reference to class type or if T is any reference type and the initializer list has no elements, a prvalue temporary of the type referenced by T is list-initialized, and the reference is bound to that temporary.
[...]
(I don't have a copy of the Standard itself right now.)
Let's assume your compiler implements the proposed resolutions to the defect reports. Then, the first example
const A& a1 = { a };
initializes like const A& a1 = a; (no temporary); and the second example
const A& a2 = { a, 1 };
initializes like const A& a2 = A(a,1);.
8.5.4/3 List-initialization of an object or reference of type T is defined as follows:
...
Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is list-initialized, and the reference is bound to
that temporary.
In your example, a1 does not bind directly to a, but to a temporary copy-constructed from a.

Function takes a reference parameter with a default value

Based on
http://www.cplusplus.com/reference/stl/vector/vector/
explicit vector ( const Allocator& = Allocator() );
This vector constructor takes a reference parameter which has default value of Allocator(). What I learn from this function signature is that a function can take a reference parameter with default value.
This the demo code I play with VS2010.
#include "stdafx.h"
#include <iostream>
using namespace std;
void funA(const int& iValue=5) // reference to a template const int 5 why?
{
cout << iValue << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
funA();
funA(10);
return 0;
}
are there some rules to guide this syntax usage (i.e. a reference parameter with a default value)?
Const references may be bound to temporary objects, in which case the lifetime of the temporary extends to the lifetime of the reference.
The only rules I can think of are (a) that the reference must be const, because you can't bind a non-const reference to a temporary, and (b) that it's generally better not to use const references to pass built-in types. In other words:
(a)
void f(T& t = T(23)) {} // bad
void g(const T& t = T(23)) {} // fine
(b)
void f(const int& i = 23) {} // sort of ok
void g(int i = 23) {} // better
This behavior is defined in § 8.3.6 5 of c++03:
A default argument expression is implicitly converted (clause 4) to the parameter type. The default argument expression has the same semantic constraints as the initializer expression in a declaration of a variable of the parameter type, using the copy-initialization semantics (8.5).
That is, const Type& var = val is a valid parameter declaration only if it's also a valid variable declaration. According to § 8.5.3 5, it is. For const Allocator& = Allocator(), the following applies:
Otherwise, the reference shall be to a non-volatile const type (i.e., cv1 shall be const). [...]
If the initializer expression is an rvalue, with T2 a class type, and "cv1 T1" is reference-compatible with "cv2 T2," the reference is bound in one of the following ways (the choice is implementation defined):
The reference is bound to the object represented by the rvalue (see 3.10) or to a sub-object within that object.
A temporary of type "cv2 T2" [sic] is created, and a constructor is called to copy the entire rvalue object into the temporary. The reference is bound to the temporary or to a sub-object within the temporary.
The constructor that would be used to make the copy shall be callable whether or not the copy is actually done. [...]
Otherwise, [...]
For const int& iValue=5, the next case applies:
Otherwise, the reference shall be to a non-volatile const type (i.e., cv1 shall be const). [...]
If the initializer expression is an rvalue[...]
Otherwise, a temporary of type "cv1 T1" is created and initialized from the initializer expression using the rules for a non-reference copy initialization (8.5). The reference is then bound to the temporary. If T1 is reference-related to T2, cv1 must be the same cv-qualification as, or greater cv-qualification than, cv2; otherwise, the program is ill-formed. [Example:
const double& rcd2 = 2; // rcd2 refers to temporary with value 2.0
const volatile int cvi = 1;
const int& r = cvi; // error: type qualifiers dropped
---end example]
In short, a real, though perhaps temporary, variable is created so the reference can refer to it. It's allowed in parameter declarations exactly so that reference parameters can take default values. Otherwise, it would be a needless restriction. The more orthogonal a language is, the easier it is to keep in your head, as you don't need to remember as many exceptions to the rules (though, arguably, allowing const references but not non-const references to be bound to rvalues is less orthogonal than disallowing any reference to be bound to an rvalue).