Should the following sample compile?
struct B;
struct A
{
A(B*&&){}
};
struct B : A
{
B() : A(this){}
};
int main(){}
On LWS with clang it compiles, but with gcc I get:
no known conversion for argument 1 from 'B* const' to 'B*&&'
and if I add a const it compiles.
I would like to also point out MSVC gets it wrong too:
cannot convert parameter 2 from 'B *const ' to 'B *&&'
so it looks like we have a bug in two compilers.
BUGS FILED
MSVC bug link
GCC bug link
Yes, that should compile.
It is incorrect to implement this as cv T* const (where cv is the cv-qualifiers for the function, if any, and T is the class type). this is not const, merely a prvalue expression of a built-in type (not modifiable).
Many people think that because you can't modify this it must be const, but as Johannes Schaub - litb once commented long ago, a much better explanation is something like this:
// by the compiler
#define this (__this + 0)
// where __this is the "real" value of this
Here it's clear that you can't modify this (say, this = nullptr), but also clear no const is necessary for such an explanation. (And the value you have in your constructor is just the value of the temporary.)
I say clang is right - the code should compile. For some reason, GCC considers the this pointer to be const despite the following:
The type of this in a member function of a class X is X*. If the member function is declared const, the type of this is const X*, if the member function is declared volatile, the type of this is volatile X*, and if the member function is declared const volatile, the type of this is const volatile X*.
So in this case, this should be a prvalue B* and perfectly bindable to B*&&. However, note that when binding this to an rvalue reference, the value of this will be copied into a temporary object and the reference will instead be bound to that. This ensures that you never actually modify the original this value.
A reference to type "cv1 T1" is initialized by an expression of type "cv2 T2" as follows:
[...]
[...] or the reference shall be an rvalue reference.
If the initializer expression
is an xvalue, class prvalue, array prvalue or function lvalue and [...], or
has a class type (i.e., T2 is a class type), [...]
then [...]
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. [...]
Related
This question already exists:
Deducing template argument of a conversion function template [duplicate]
Closed 6 months ago.
Consider the following example:
struct S
{
template <class T>
operator const T()
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
return T();
}
} s;
int&& res = s;
Per [temp.deduct.conv]/1: (emphasis mine)
Template argument deduction is done by comparing the return type of
the conversion function template (call it P) with the type that is
required as the result of the conversion (call it A) [..]
and [temp.deduct.conv]/4: (emphasis mine)
If A is a cv-qualified type, the top-level cv-qualifiers of A's type
are ignored for type deduction. If A is a reference type, the type
referred to by A is used for type deduction.
and [temp.deduct.conv]/5 (emphasis mine)
In general, the deduction process attempts to find template argument
values that will make the deduced A identical to A. However, there are
four cases that allow a difference:
(5.1) If the original A is a
reference type, A can be more cv-qualified than the deduced A (i.e.,
the type referred to by the reference).
[..]
I'm expecting either of the following is occurring:
The types of P and A will be: P = const T, A = int&& Now, since A is a reference type, no adjustments occurred in P. But the type A would be adjusted per [temp.deduct.conv]/4. So the final types of P and A is P = const T, A = int. Now we've ended up with a deduction failure, so alternative deductions defined in [temp.deduct.conv]/5 are considered. The bullet (5.1) is what's needed in this case. Now A (int) is not more cv-qualified than deduced A (int). So the program is ill-formed because the initialization is invalid.
The reference initialization is invalid duto binding a reference of type int&& to a value of type const Twill drop qualifiers. So the program is ill-formed because the initialization is invalid..
Unfortunately, nothing of what I expected happened. The Live Demo clearly shows that the deduced T for the above example is int.
My question: What am I missing/conflating here?
N4861, to which you link, is not the newest working draft. I'm going to assume that you linked to it because it's the closest draft to C++20. The quotations that I'm going to provide below are from C++20 itself, just in case there are any differences.
According to [temp.deduct.conv]/1, we need to consult [dcl.init], [over.match.conv], and [over.match.ref] to determine what is A.
And in fact, [dcl.init] is really where you should have started looking in the first place, since the overload resolution is triggered by the declaration int&& res = s;.
[dcl.init.ref]/5.3 and 5.4 govern the initialization of rvalue references. 5.3 is tried first:
Otherwise, if the initializer expression
is an rvalue (but not a bit-field) or function lvalue and "cv1 T1" is reference-compatible with "cv2 T2", or
has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be converted to an rvalue or function lvalue of type "cv3 T3", where "cv1 T1" is reference-compatible with "cv3 T3*" (see [over.match.ref]),
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. In any case, the reference is bound to the resulting glvalue (or to an appropriate base class subobject).
So we need to consult [over.match.ref] to see if such a cv3 T3 exists (in which case the conversion will be performed, and res will be bound to the result of that conversion). (Note that [over.match.conv] will not be reached in this case; it would be reached if we got to [dcl.init.ref]/5.4, but we will not, because [dcl.init.ref]/5.3 will succeed.)
[over.match.ref] says:
Under the conditions specified in [dcl.init.ref], a reference can be bound directly to the result of applying a conversion function to an initializer expression. Overload resolution is used to select the conversion function to be invoked. Assuming that "reference to cv1 T" is the type of the reference being initialized, and "cv S" is the type of the initializer expression, with S a class type, the candidate functions are selected as follows:
The conversion functions of S and its base classes are considered. Those non-explicit conversion functions that are not hidden within S and yield type "lvalue reference to cv2 T2" (when initializing an lvalue
reference or an rvalue reference to function) or "cv2 T2" or "rvalue reference to cv2 T2" (when initializing an rvalue reference or an lvalue reference to function), where "cv1 T" is reference-compatible ([dcl.init.ref]) with "cv2 T2", are candidate functions. For direct-initialization, those explicit conversion functions that are not hidden within S and yield type "lvalue reference to cv2 T2" (when initializing an lvalue reference or an rvalue reference to function) or "rvalue reference to cv2 T2" (when initializing an rvalue reference or an lvalue reference to function), where T2 is the same type as T or can be converted to
type T with a qualification conversion ([conv.qual]), are also candidate functions.
The argument list has one argument, which is the initializer expression.
The only type that int is reference-compatible with is int itself, and we are initializing an rvalue reference, so according to this section, we look for conversion functions of S with type int and int&&.
Only at this point, we are ready to perform template argument deduction under [temp.deduct.conv]/1. We will need to do this twice: once with A = int, and once with A = int&&. The results are then merged, and if there is more than one candidate in the resulting set, then we need to use overload resolution to select the one that gets called.
With A = int, [temp.deduct.conv]/3.3 tells us that the top-level cv-qualifiers of P are ignored. Clearly, this results in T being deduced as int; the candidate is S::operator const int().
With A = int&&, in this case the top-level cv-qualifiers of P are not ignored; P remains const T. [temp.deduct.conv]/4 tells us that A is transformed into the type it refers to, namely int. We do the deduction using the transformed types (P = const T, A = int). This fails; there's no T such that const T is int. So there are no candidates generated in this step.
The result is that S::operator const int() is called, producing a prvalue of type const int, but cv-qualification of prvalues of scalar type is discarded prior to any further analysis. So it is materialized into a temporary object of type int, and res is bound to that temporary.
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;
class A{
public:
virtual ~A() {};
};
class B : public A{ };
int main(){
A&& p = B();
dynamic_cast<B&&>(std::move(p));
}
Throws the error (g++ 5.2.0):
error: conversion to non-const reference type 'std::remove_reference<A&>::type& {aka class A&}' from rvalue of type 'A' [-fpermissive]
It attempts to cast std::move(p) to type A&, but I cannot figure out why. I would've thought it necessary to cast p as an rvalue before converting to an rvalue reference, but if I remove std::move it compiles fine. From cppreference:
dynamic_cast < new_type > ( expression )
Similar to other cast expressions, the result is:
an lvalue if new_type is an lvalue reference type (expression must be an lvalue)
an xvalue if new_type is an rvalue reference type (expression may be lvalue or rvalue)
Even 5.2.7 of N3337:
dynamic_cast<T>(v)
If T is a pointer type, v shall be a prvalue of a pointer to complete class type, and the result is a prvalue of type T. If T is an lvalue reference type, v shall be an lvalue of a complete class type, and the result is an lvalue of the type referred to by T. If T is an rvalue reference type, v shall be an expression having a complete class type, and the result is an xvalue of the type referred to by T.
The only requirement there being that I use a complete class type, which std::move(p) is, isn't it?
Your code is, of course, fine:
If T is an rvalue reference type, v shall be an expression having a
complete class type, and the result is an xvalue of the type referred
to by T.
Presumably, dynamic_cast wasn't updated properly when rvalue references were introduced, and still enforces the pre-C++11 rule that rvalues shall only be bound to const lvalue references (note that it doesn't even work when altering the target type to B const&&, despite that being implied by the error message!).
Filed as #69390.
This seems to work instead:
B&& b = std::move(dynamic_cast<B&>(p));
Can't tell you why yours is incorrect.
Should the following sample compile?
struct B;
struct A
{
A(B*&&){}
};
struct B : A
{
B() : A(this){}
};
int main(){}
On LWS with clang it compiles, but with gcc I get:
no known conversion for argument 1 from 'B* const' to 'B*&&'
and if I add a const it compiles.
I would like to also point out MSVC gets it wrong too:
cannot convert parameter 2 from 'B *const ' to 'B *&&'
so it looks like we have a bug in two compilers.
BUGS FILED
MSVC bug link
GCC bug link
Yes, that should compile.
It is incorrect to implement this as cv T* const (where cv is the cv-qualifiers for the function, if any, and T is the class type). this is not const, merely a prvalue expression of a built-in type (not modifiable).
Many people think that because you can't modify this it must be const, but as Johannes Schaub - litb once commented long ago, a much better explanation is something like this:
// by the compiler
#define this (__this + 0)
// where __this is the "real" value of this
Here it's clear that you can't modify this (say, this = nullptr), but also clear no const is necessary for such an explanation. (And the value you have in your constructor is just the value of the temporary.)
I say clang is right - the code should compile. For some reason, GCC considers the this pointer to be const despite the following:
The type of this in a member function of a class X is X*. If the member function is declared const, the type of this is const X*, if the member function is declared volatile, the type of this is volatile X*, and if the member function is declared const volatile, the type of this is const volatile X*.
So in this case, this should be a prvalue B* and perfectly bindable to B*&&. However, note that when binding this to an rvalue reference, the value of this will be copied into a temporary object and the reference will instead be bound to that. This ensures that you never actually modify the original this value.
A reference to type "cv1 T1" is initialized by an expression of type "cv2 T2" as follows:
[...]
[...] or the reference shall be an rvalue reference.
If the initializer expression
is an xvalue, class prvalue, array prvalue or function lvalue and [...], or
has a class type (i.e., T2 is a class type), [...]
then [...]
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. [...]
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).