Reference to the this pointer: GCC vs clang - c++

This is a follow-up of these questions.
Consider the following code:
struct A {
private:
A* const& this_ref{this};
};
int main() {
A a{};
(void)a;
}
If compiled with the -Wextra, both GCC v6.2 and clang v3.9 show a warning.
Anyway, with the slightly modified version shown below they behave differently:
struct A {
A* const& this_ref{this};
};
int main() {
A a{};
(void)a;
}
In this case GCC doesn't give any warning, clang gives the same warning as returned in the previous example.
The warnings are almost the identical.
It follows the one from clang:
3 : warning: binding reference member 'this_ref' to a temporary value [-Wdangling-field]
Which compiler is right?
I would say that GCC is wrong in this case and I were opening an issue, but maybe it's the opposite because of an arcane corner case of the language.

The member declaration
A* const& this_ref{this};
binds a reference to a temporary that only exists during constructor execution (note: this is an rvalue expression).
I'm not sure if this is formally available in that context, but if it is then with any use of that pointer you have serious case of UB.
Re
” Which compiler is right?
… a compiler can issue as many diagnostics as it wants. It's not wrong to issue a diagnostic. So per your description, that both accept the code, then either both compilers are right (which I think is most likely), or both are wrong.

The reason for this warning is IMO this excerpt from standard (12.2.5):
A temporary bound to a reference member in a constructor’s ctor-initializer (12.6.2) persists until the
constructor exits.
and since the keyword this is a prvalue expression, during this_ref initialization a temporary will be created and this_ref is bound to that temporary.
But I have doubt whether your reference is actually initialized in ctor-initializer.
If you write:
struct A {
private:
const int& rr = 1+1;
};
then you will reproduce the exact same problem with gcc, removing private will also remove this warning.
From what I know this pointer might be used in the body of the non-static member function, I have never read that it could be used as argument during default member initialization.

this is prvalue, and temporary object will be created when binding reference to a prvalue, so you're binding reference member to a temporary in default member initializer.
And binding reference member to temporary in default member initializer is ill-formed, which is stated by the standard explicitly.
$12.6.2/11 Initializing bases and members
[class.base.init]:
A temporary expression bound to a reference member from a default
member initializer is ill-formed. [ Example:
struct A {
A() = default; // OK
A(int v) : v(v) { } // OK
const int& v = 42; // OK
};
A a1; // error: ill-formed binding of temporary to reference
A a2(1); // OK, unfortunately
— end example ]
And see CWG 1696, this is applied to C++14.

Related

Is it possible to initialize member variable (or base class) of a non-copyable type?

Consider following code:
struct S
{
S() = default;
S(S const&) = delete;
// S(S&&) = delete; // <--- uncomment for a mind-blowing effect:
// MSVC starts compiling EVERY case O_O
};
S foo() { return {}; }
struct X : S
{
// X() : S(foo()) {} // <----- all compilers fail here
};
struct Y
{
S s;
Y() : s(foo()) {} // <----- only MSVC fails here
};
struct Z
{
S s = {}; // ... and yet this is fine with every compiler
Z() {}
};
//S s1(foo()); // <-- only MSVC fails here
//S s2 = foo(); // <-- only MSVC fails here
Questions:
It looks like there is no way to initialize non-copyable base class with a prvalue -- is this correct? Looks like a deficiency in standard (or all compilers I tried are non-compliant)
MSVC can't initialize member variable -- does it mean it is non-compliant? Is there a way to workaround this?
why adding S(S&&) = delete; causes MSVC to compile every case?
So, I think I found the relevant parts of the standard and I think the compilers are in error regarding to X. (All links are to a standard draft so very maybe it was different in C++17, I will check that later. But gcc10 and clang10 also fail with -std=c++20, so that is not that important).
Regarding the initialization of base classes (emphasis mine): class.base.init/7
The expression-list or braced-init-list in a mem-initializer is used to initialize the designated subobject (or, in the case of a delegating constructor, the complete class object) according to the initialization rules of [dcl.init] for direct-initialization.
I think this tells us, that X() : S(foo()) {} should not be different from S s = foo(), but let's look at dcl.init/17.6.1
If the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object. [Example: T x = T(T(T())); calls the T default constructor to initialize x. — end example]
This implies to me, that X() : S(foo()) {} should call the default constructor. I also tested (to be completely in line with the example) X() : S(S()) {} and this also fails on clang and g++. So it seems to me that the compilers have a defect.
It looks like there is no way to initialize non-copyable base class with a prvalue -- is this correct? Looks like a deficiency in standard (or all compilers I tried are non-compliant)
The standard says that it should work. The standard is wrong.
A base class subobject (more generally, a potentially-overlapping subobject) may have a different layout from a complete object of the same type, or may have its padding reused by other objects. It is therefore impossible to elide a copy or move from a function returning a prvalue, since the function has no idea that it is not initializing a complete object.
The rest are MSVC bugs.
No, it's not allowed, but since c++17, there are some new features, and one of them, functions no more copy object.
Functions returning prvalues no longer copy objects (mandatory copy elision), and there is a new prvalue-to-glvalue conversion called
temporary materialization conversion. This change means that copy
elision is now guaranteed, and even applies to types that are not
copyable or movable. This allows you to define functions that return
such types.
Guaranteed copy elision C++17
The following function, never return S() object and return, std::initialization_list or {}, instead, as S s ={}, is a valid conversion, so based on copy elision optimization, it's not going to return a copy and roughly speaking - directly return std::initialization_list itself. Note temporary materialization conversion.
S foo() { return {}; }
The following not going to work,
S foo() { S s = {}; return s; }
So that, the line Y() : s(foo()) {}, roughly speaking - now could be interpreted as implicit type conversion,
S s = {}

Initialize rvalue reference member

I am trying to initialize a rvalue reference member in the following situations: struct A is a aggregate and class B has a user defined constructor so it is not an aggregate. According to cppreference here,
struct A {
int&& r;
};
A a1{7}; // OK, lifetime is extended
A a2(7); // well-formed, but dangling reference
The reference in my struct A should be correctly initialized and the temporary string should be extended, but it's not.
For my class B, in the same cppreference page:
a temporary bound to a reference member in a constructor initializer list persists only until the constructor exits, not as long as the object exists. (note: such initialization is ill-formed as of DR 1696).
(until C++14)
But I am still getting problem with MSVC with /std:c++latest. Am I missing anything?
struct A
{
std::string&& ref;
};
class B
{
std::string&& ref;
public:
B(std::string&& rref):ref(std::move(rref)){}
void print() {std::cout<<ref<<'\n';}
};
int main()
{
A a{ std::string{"hello world"} };
std::cout<<a.ref<<'\n'; //garbage in MSVC with c++latest, correct in GCC9.2
B b{ std::string{"hello world"} };
b.print(); //same
}
EDIT: Please tell me if I am getting dangling reference in these two cases in the first place. And for MSVC, the version is the latest update, v19.24.28315
EDIT2: OK I am actually confused by cppreference's explanation. I am not asking specificly for C++20. Please tell me under which C++ version, the code is well-formed or not.
EDIT3: Is there a proper way to initialize a rvalue reference member of a non-aggregate class? Since bind it to a temporary in a member initializer list is ill-formed (until C++14, does it mean it is good since C++14?) and passing a temporary to a constructor expecting an rvalue cannot extend its lifetime twice.
Your class A is an aggregate type, but B isn't, because it has a user-provided constructor and a private non-static member.
Therefore A a{ std::string{"hello world"} }; is aggregate initialization which does extend the lifetime of the temporary through reference binding to that of a.
On the other hand B is not aggregate initialization. It calls the user-provided constructor, which passes on the reference. Passing on references does not extend the lifetime of the temporary. The std::string object will be destroyed when the constructor of B exits.
Therefore the later use of a has well-defined behavior, while that of b will have undefined behavior.
This holds for all C++ versions since C++11 (before that the program would be obviously ill-formed).
If your compiler is printing garbage for a (after removing b from the program so that it doesn't have undefined behavior anymore), then this is a bug in the compiler.
Regarding edit of question:
There is no way to extend the lifetime of a temporary through binding to a reference member of a non-aggregate class.
Relying on this lifetime extension at all is very dangerous, since you would likely not get any error or warning if you happen to make the class non-aggregate in the future.
If you want the class to always retain the object until its lifetime ends, then just store it by-value instead of by-reference:
class B
{
std::string str;
public:
B(std::string str) : str(std::move(str)) {}
void print() { std::cout << str << '\n'; }
};

C++11 scoping and lifetime of temporary bound to a (const) reference (GCC)

I have the following questions related to the same situation (not in general):
Why does the compiler not produce a warning when a temporary is bound
to a reference?
How does lifetime extension of a temporary work (when it is bound to
a reference)?
How to interpret / understand the C++ standard (C++11)?
Is this a bug (in the compiler)? Should it be?
So this is what we are talking about:
struct TestRefInt {
TestRefInt(const int& a) : a_(a) {};
void DoSomething() { cout << "int:" << a_ << endl; }
protected:
const int& a_;
};
Should TestRefInt tfi(55); and tfi.DoSomething(); work? How and where?
So here is a code
TestRefInt tfi(55);
int main() {
TestRefInt ltfi(8);
tfi.DoSomething();
ltfi.DoSomething();
return 0;
}
What should this do?
Here I will elaborate some more.
Consider this real world (simplified) example. How does it look like to a novice/beginner C++ programmer? Does this make sense?
(you can skip the code the issue is the same as above)
#include <iostream>
using namespace std;
class TestPin //: private NonCopyable
{
public:
constexpr TestPin(int pin) : pin_(pin) {}
inline void Flip() const {
cout << " I'm flipping out man : " << pin_ << endl;
}
protected:
const int pin_;
};
class TestRef {
public:
TestRef(const TestPin& a) : a_(a) {};
void DoSomething() { a_.Flip(); }
protected:
const TestPin& a_;
};
TestRef g_tf(1);
int main() {
TestRef tf(2);
g_tf.DoSomething();
tf.DoSomething();
return 0;
}
Command line:
/** Compile:
Info: Internal Builder is used for build
g++ -std=c++11 -O0 -g3 -Wall -Wextra -Wconversion -c -fmessage-length=0 -o "src\\Scoping.o" "..\\src\\Scoping.cpp"
g++ -o Scoping.exe "src\\Scoping.o"
13:21:39 Build Finished (took 346ms)
*/
Output:
/**
I'm flipping out man : 2293248
I'm flipping out man : 2
*/
The issue:
TestRef g_tf(1); /// shouldn't the reference to temporary extend it's life in global scope too?
What does the standard says?
From How would auto&& extend the life-time of the temporary object?
Normally, a temporary object lasts only until the end of the full
expression in which it appears. However, C++ deliberately specifies
that binding a temporary object to a reference to const on the stack
lengthens the lifetime of the temporary to the lifetime of the
reference itself
^^^^^
Globals are not allocated on stack, so lifetime is not extended. However, the compiler does not produce any warning message whatsoever! Shouldn’t it at least do that?
But my main point is: from a usability standpoint (meaning as the user of C++, GCC as a programmer) it would be useful if the same principle would be extended to not just stack, but to global scope, extending the lifetime (making global temporaries “permanent”).
Sidenote:
The problem is further complicated by the fact that the TestRef g_tf(1); is really TestRef g_tf{ TestPin{1} }; but the temporary object is not visible in the code, and without looking at the constructor declaration it looks like the constructor is called by an integer, and that rarely produces this kind of error.
As far as I know the only way to fix the problem is to disallow temporaries in initialization by deleting TestRefInt(const int&& a) =delete; constructor. But this also disallows it on stack, where lifetime extension worked.
But the previous quote is not exactly what the C++11 standard say. The relevant parts of the C++11 Standard are 12.2 p4 and p5:
4 - There are two contexts in which temporaries are destroyed at a
different point than the end of the full expression. The first context
is [...]
5 - The second context is when a reference is bound to a
temporary. The temporary to which the reference is bound or the
temporary that is the complete object of a subobject to which the
reference is bound persists for the lifetime of the reference except:
— A temporary bound to a reference member in a constructor’s
ctor-initializer (12.6.2) persists until the constructor exits.
— A temporary bound to a reference parameter in a function call (5.2.2)
persists until the completion of the full-expression containing the
call. § 12.2 245 c ISO/IEC N3337
— The lifetime of a temporary bound
to the returned value in a function return statement (6.6.3) is not
extended; the temporary is destroyed at the end of the full-expression
in the return statement.
— A temporary bound to a reference in a
new-initializer (5.3.4) persists until the completion of the
full-expression containing the new-initializer. [Example: struct S {
int mi; const std::pair& mp; }; S a { 1, {2,3} }; S* p = new S{ 1,
{2,3} }; // Creates dangling reference — end example ] [ Note: This
may introduce a dangling reference, and implementations are encouraged
to issue a warning in such a case. — end note ]
My English and understanding of the standard is not good enough, so what exception is this?
The “— A temporary bound to a reference member in a constructor’s ctor-initializer (12.6.2) persists until the constructor exits.” or “reference parameter in a function call” does not mention anything about allocation on stack or in global scope. The other exceptions do not seem to apply. Am I wrong?
Which one is this, or none of them?
We have a temporary in a function call (the constructor) and then a reference to
the temporary is bound to a member reference in the initializer. Is this undefined behavior? Or the exception still applies, and the temporary should be destroyed? (or both)
What about this?
struct TestRefIntDirect {
TestRefIntDirect(int a) : a_(a) {};
void DoSomething() { cout << "int:" << a_ << endl; }
protected:
const int& a_;
};
One less reference, same behavior.
Why does it work in one case (instantiation inside a function) versus in other case (in global scope)?
Does GCC not “destroy” one of them by “accident”?
My understanding is that none of them should work, the temporary should not persist as the standard says. It seems GCC just "lets" you access not persisting objects (sometimes). I guess that the standard does not specify what the compiler should warn about, but can we agree that it should? (in other cases it does warn about ‘returning reference to temporary’) I think it should here too.
Is this a bug or maybe there should be a feature request somewhere?
It seems to me like GCC says “Well, you shouldn’t touch this cookie, but I will leave it here for you and not warn anyone about it.” ...which I don’t like.
This thing others reference about stack I did not find in the standard, where is it coming from? Is this misinformation about the standard? Or is this a consequence of something in the standard? (Or is this just implementation coincidence, that the temporary is not overwritten and can be referenced, because it happened to be on the stack? Or is this compiler defined behavior, extension?)
The more I know C++ the more it seems that every day it is handing me a gun to shoot myself in the foot with it…
I would like to be warned by the compiler if I’m doing something wrong, so I can fix it. Is that too much to ask?
Other related posts:
Temporary lifetime extension
C++: constant reference to temporary
Does a const reference prolong the life of a temporary?
Returning temporary object and binding to const reference
I didn’t want to write this much. If you read it all, thanks.

r-value causes a warning without the use of std::move

Can someone help me to understand why the following code causes a warning
struct A
{
A() : _a( 0 ) {}
const int& _a;
};
int main()
{
A a;
}
with warning
warning: binding reference member '_a' to a temporary value [-Wdangling-field]
A() : _a( 0 ) {}
but this code, where std::move is used to initialize the member _a, does not:
struct A
{
A() : _a( std::move(0) ) {}
const int& _a;
};
int main()
{
A a;
}
Aren't 0 and std::move( 0 ) both r-values?
This is an expression:
0
It's a very small expression, true. But it is an expression.
Once the expression is evaluated, it goes away. It disappears. Joins the choir invisible. Goes to meet its maker. It becomes an ex-expression.
It is true that binding a const reference to a temporary extends the scope of the temporary value until the end of the enclosing scope.
But in this case, the scope of the expression is the constructor. When the constructor is done, the temporary value gets destroyed.
Your compiler noticed the fact that a const reference to the expression still continues to exist, though, as a class member. Your compiler is advising you that using the class member will now result in undefined behavior. Your compiler wants to be your friend. Your compiler doesn't want you to write buggy code, so you're getting some free, friendly advice, from your compiler.
In the other case, you have added some additional code which is slightly more complicated. It is still undefined behavior, but the code is now complex enough that the compiler cannot see that undefined behavior results. But it's still the same bug.
A compiler will try to warn you of potential problems, when the compiler sees them. Unfortunately, the compiler cannot find all possible potential problems, every time. But, when it's obvious, the compiler will let you know.
Their return values are not exactly the same.
From cppreference.com
In particular, std::move produces an xvalue expression that identifies
its argument t. It is exactly equivalent to a static_cast to an rvalue
reference type.
Now, looking at rvalue references, we see that object "0" in second example can live longer:
An rvalue may be used to initialize an rvalue reference, in which case
the lifetime of the object identified by the rvalue is extended until
the scope of the reference ends.
Such reference (rvalue reference) is afterwards assigned to the class member _a, which is allowed, so you are having no error.
Moreover, rvalue reference to a temporary can be used in a move constructor, so if the member you are initialising has it, I don't see the problem. However, in C++ you can never know when undefined behaviour can suddenly hit you :)

Initialization of const reference member in initializer list

I was playing with some useless code to understand initialization of member references, and bumped into this:
struct A {};
struct B
{
B() : a()
{
}
const A& a;
};
The code above gives the following error when compiled with gcc 4.9.2:
In constructor 'B::B()':
error: value-initialization of reference type 'const A&'
B() : a()
Which I understand.
But if I use uniform initialization in B's constructor's initializer list, like so:
struct A {};
struct B
{
B() : a{}
{
}
const A& a;
};
It compiles fine.
So the question is, why does the use of uniform initialization here change the compilation result?
I also tried this with Microsoft Visual C++ 2013.
It does not compile either version of the code, with the same error message:
Error 3 error C2440: 'initializing' : cannot convert from 'int' to 'const A &
You can have a quick play with it here:
http://ideone.com/7f2t8I
GCC is correct in its interpretation of {}. [dcl.init.list]/p3.8-9 (quoting N4296; earlier drafts has the same relative ordering of these two bullets):
List-initialization of an object or reference of type T is defined as
follows:
[7 inapplicable bullets omitted]
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. [ Note:
As usual, the binding will fail and the program is ill-formed if the
reference type is an lvalue reference to a non-const type. —end note
]
Otherwise, if the initializer list has no elements, the object is value-initialized.
List-initializing the reference hits bullet 3.8, causing the construction of a temporary. The value-initialization case, in 3.9, doesn't apply.
Value-initialization of a reference is ill-formed ([dcl.init]/p9):
A program that calls for default-initialization or
value-initialization of an entity of reference type is ill-formed.
However, as of N4296, per [class.base.init]/p8:
A temporary expression bound to a reference member in a
mem-initializer is ill-formed.
This was added as a result of CWG issue 1696, which is a DR (defect report) against C++14.
Pre-CWG1696, the standard provided that (N4140 [class.temporary]/p5.1):
A temporary bound to a reference member in a constructor’s
ctor-initializer (12.6.2) persists until the constructor exits.
which means that the reference will become dangling immediately after construction. This presumably motivated CWG1696's decision to disallow such bindings altogether.