Should constexpr initialization happen before other initialization - c++

I have the following piece of code, that behaves as expected on gcc and clang. However, MSVC gives me an unexpected result.
Lets first look at the problematic code.
#include <iostream>
// -----------------------------------------------
class Test // Dummy for MCVE
{
public:
Test();
void Print();
private:
int arr[5];
};
Test tst;
// -----------------------------------------------
template<typename T>
struct range // some stuff not needed by example removed
{
constexpr range(T n) : b(0), e(n) {}
constexpr range(T b, T e) : b(b), e(e) {}
struct iterator
{
T operator*() { return i; }
iterator& operator++() { ++i; return *this; }
bool operator!=(iterator other) { return i != other.i ; }
T i;
};
iterator begin() const { return{ b }; }
iterator end() const { return{ e }; }
private:
T b,e;
};
constexpr range<int> coord(5);
// -----------------------------------------------
Test::Test()
{
for(auto i : coord)
arr[i]=i;
}
void Test::Print()
{
for(auto i : coord)
std::cout << arr[i] << std::endl;
}
// -----------------------------------------------
int main()
{
tst.Print();
}
Now, on both clang and gcc this prints '0 1 2 3 4'
However, on MSVC this prints '0 0 0 0 0'
The reason being that when the constructor on the global variable tst
runs, 'coord' have yet not been initialized (to 0,5), but it is also not random, but rather (0,0).
To me it would make sense that constexpr initialization happens before regular initialization. Is MSVC conformant in this behaviour?
I should perhaps note that I'm using MSVC version 14.0.22823.1, and
that the expected result can be obtained by changing the order of
the declarations

For static storage duration objects initialization must happen in this order:
Zero-initialization.
Constant initialization (i.e. constexpr).
Dynamic initialization.
Relevant standardeese,
C++14 §3.6.2/2:
” Variables with static storage duration (3.7.1) or thread storage duration (3.7.2) shall be zero-initialized (8.5)
before any other initialization takes place. […] Constant initialization is performed: […] if an object with static or thread storage duration is initialized by a constructor call, and if the
initialization full-expression is a constant initializer for the object; […] Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. Static initialization shall be performed before any dynamic initialization takes place.
The same paragraph defines (breaking the flow of text, so I removed it above)
” A constant initializer for an object o is an expression that is a constant expression, except that it may also invoke constexpr constructors for o and its subobjects even if those objects are of non-literal class types.
In the reported example, which I've verified with Visual C++ 2015, the dynamic initialization of the static storage duration object tst takes place before the constant initialization of the static storage duration object coord, and that's a compiler bug.

Related

constexpr if and the return value optimization

I have this code:
#include <string>
class A {
public:
// A(A const &) = delete; // Code fails if this is uncommented.
explicit A(int);
explicit A(::std::string const &);
private:
::std::string myname_;
int foo_;
};
static constexpr bool which = false;
A test(::std::string const &s, int a)
{
if constexpr (which) {
A x{a};
return x;
} else {
A y{s};
return y;
}
}
This code fails if A has a deleted copy constructor. But, given the rules about return type for functions with if constexpr in them, it seems like the compiler should be applying RVO here.
Is there a reason why it isn't, other than it being an overlooked case in the language specification?
This has nothing to do with if constexpr
Simply this code is not allowed to compile:
class A {
public:
A(A const &) = delete;
explicit A(int);
};
A test(int a)
{
A x{a};
return x; // <-- error call to a deleted constructor `A(A const &) = delete;`
}
The changes in C++17 you are thinking of have to do with temporary materialization and don't apply to NRVO because x is not a prvalue.
For instance this code was illegal pre C++17 and now it is allowed:
A test(int a)
{
return A{a}; // legal since C++17
}
This is NRVO, which is non-mandatory copy elision:
(emphasis mine)
In a return statement, when the operand is the name of a non-volatile object with automatic storage duration, which isn't a
function parameter or a catch clause parameter, and which is of the
same class type (ignoring cv-qualification) as the function return
type. This variant of copy elision is known as NRVO, "named return
value optimization".
This is an optimization: even when it takes place and the copy/move
(since C++11) constructor is not called, it still must be present and
accessible (as if no optimization happened at all), otherwise the
program is ill-formed:
BTW: Note that in your code, both the if part and else part of the constexpr if statement will be checked.
Outside a template, a discarded statement is fully checked. if
constexpr is not a substitute for the #if preprocessing directive:
void f() {
if constexpr(false) {
int i = 0;
int *p = i; // Error even though in discarded statement
}
}

constexpr and mutable member and implicit copy-ctor

The following code compiles in clang 7+, but not in 5 & 6 (with c++17 and c++14).
The problem for clang 5 and 6 seems to be that the implicit copy ctor reads from the mutable member x.
Can anybody tell me, if the whole construction is standard-compliant (c++17), or if the program is ill-formed? Or was there a change in the standard regarding the implicit copy-ctor which might not be implemented in the earlier clang versions?
struct Foo {
int a;
mutable int x{};
constexpr Foo() : a(0) {}
//constexpr Foo(const Foo& other) : a(other.a) {} // <- with this line it works on Clang 5 & 6, too
};
struct FooFactory {
static constexpr auto create() {
auto f = Foo{};
return f;
}
};
int main() {
constexpr Foo f = FooFactory::create();
++f.x;
}
Live code here.
This is a well-formed C++17 program. A constexpr function can of course read (and write) non-constant variables:
constexpr int f(int i) {
int j=i;
++j;
return i+j; // neither is a constant expression
}
The rule is that anything examined in a constant expression must either be a constant or have its lifetime begun during the expression’s evaluation. In your case, the lifetime of create’s f.x plainly begins within the evaluation of the constant expression that is the initialization of main’s f. It is true, however, that no Foo object can be copied by a constant expression that does not also create that object, whether or not it is constexpr.
The only other candidate problem is if the copy constructor were not constexpr, but those requirements are very weak. The only relevant ones are that every (non-variant) member be initialized, which is certainly satisfied, and that it be usable in at least one constant expression, which has been demonstrated.
The code is ill-formed. For the reference, here is simplified code, which fails on all compilers:
struct Foo {
int a;
mutable int x{};
constexpr Foo() : a(0) {}
};
int main() {
constexpr Foo f;
constexpr Foo f1 = f;
}
As per [expr.const] 7.7,
A variable is usable in constant expressions after its initializing
declaration is encountered if
it is a constexpr variable,
or ... (3.5) a non-mutable
subobject or reference member of any of the above.
This disqualifies default copy constructors.

C++ Lambda Code Generation with Init Captures in C++ 14

I am trying to understand / clarify the code code that is generated when captures are passed to lambdas especially in generalized init captures added in C++14.
Give the following code samples listed below this is my current understanding of what the compiler will generate.
Case 1: capture by value / default capture by value
int x = 6;
auto lambda = [x]() { std::cout << x << std::endl; };
Would equate to:
class __some_compiler_generated_name {
public:
__some_compiler_generated_name(int x) : __x{x}{}
void operator()() const { std::cout << __x << std::endl;}
private:
int __x;
};
So there are multiple copies, one to copy into the constructor parameter and one to copy into the member, which would be expensive for types like vector etc.
Case 2: capture by reference / default capture by reference
int x = 6;
auto lambda = [&x]() { std::cout << x << std::endl; };
Would equate to:
class __some_compiler_generated_name {
public:
__some_compiler_generated_name(int& x) : x_{x}{}
void operator()() const { std::cout << x << std::endl;}
private:
int& x_;
};
The parameter is a reference and the member is a reference so no copies. Nice for types like vector etc.
Case 3:
Generalised init capture
auto lambda = [x = 33]() { std::cout << x << std::endl; };
My under standing is this is similar to Case 1 in the sense
that it is copied into to the member.
My guess is that the compiler generates code similar to...
class __some_compiler_generated_name {
public:
__some_compiler_generated_name() : __x{33}{}
void operator()() const { std::cout << __x << std::endl;}
private:
int __x;
};
Also if I have the following:
auto l = [p = std::move(unique_ptr_var)]() {
// do something with unique_ptr_var
};
What would the constructor look like? Does it also move it into the member?
Case 1 [x](){}: The generated constructor will accept its argument by possibly const-qualified reference to avoid unnecessary copies:
__some_compiler_generated_name(const int& x) : x_{x}{}
Case 2 [x&](){}: Your assumptions here are correct, x is passed and stored by reference.
Case 3 [x = 33](){}: Again correct, x is initialized by value.
Case 4 [p = std::move(unique_ptr_var)]: The constructor will look like this:
__some_compiler_generated_name(std::unique_ptr<SomeType>&& x) :
x_{std::move(x)}{}
so yes, the unique_ptr_var is "moved into" the closure. See also Scott Meyer's Item 32 in Effective Modern C++ ("Use init capture to move objects into closures").
There's less of a need to speculate, using cppinsights.io.
Case 1:
Code
#include <memory>
int main() {
int x = 33;
auto lambda = [x]() { std::cout << x << std::endl; };
}
Compiler generates
#include <iostream>
int main()
{
int x = 6;
class __lambda_5_16
{
int x;
public:
inline void operator()() const
{
std::cout.operator<<(x).operator<<(std::endl);
}
// inline /*constexpr */ __lambda_5_16(const __lambda_5_16 &) = default;
// inline /*constexpr */ __lambda_5_16(__lambda_5_16 &&) noexcept = default;
public: __lambda_5_16(int _x)
: x{_x}
{}
};
__lambda_5_16 lambda = __lambda_5_16(__lambda_5_16{x});
}
Case 2:
Code
#include <iostream>
#include <memory>
int main() {
int x = 33;
auto lambda = [&x]() { std::cout << x << std::endl; };
}
Compiler generates
#include <iostream>
int main()
{
int x = 6;
class __lambda_5_16
{
int & x;
public:
inline void operator()() const
{
std::cout.operator<<(x).operator<<(std::endl);
}
// inline /*constexpr */ __lambda_5_16(const __lambda_5_16 &) = default;
// inline /*constexpr */ __lambda_5_16(__lambda_5_16 &&) noexcept = default;
public: __lambda_5_16(int & _x)
: x{_x}
{}
};
__lambda_5_16 lambda = __lambda_5_16(__lambda_5_16{x});
}
Case 3:
Code
#include <iostream>
int main() {
auto lambda = [x = 33]() { std::cout << x << std::endl; };
}
Compiler generates
#include <iostream>
int main()
{
class __lambda_4_16
{
int x;
public:
inline void operator()() const
{
std::cout.operator<<(x).operator<<(std::endl);
}
// inline /*constexpr */ __lambda_4_16(const __lambda_4_16 &) = default;
// inline /*constexpr */ __lambda_4_16(__lambda_4_16 &&) noexcept = default;
public: __lambda_4_16(int _x)
: x{_x}
{}
};
__lambda_4_16 lambda = __lambda_4_16(__lambda_4_16{33});
}
Case 4 (unofficially):
Code
#include <iostream>
#include <memory>
int main() {
auto x = std::make_unique<int>(33);
auto lambda = [x = std::move(x)]() { std::cout << *x << std::endl; };
}
Compiler generates
// EDITED output to minimize horizontal scrolling
#include <iostream>
#include <memory>
int main()
{
std::unique_ptr<int, std::default_delete<int> > x =
std::unique_ptr<int, std::default_delete<int> >(std::make_unique<int>(33));
class __lambda_6_16
{
std::unique_ptr<int, std::default_delete<int> > x;
public:
inline void operator()() const
{
std::cout.operator<<(x.operator*()).operator<<(std::endl);
}
// inline __lambda_6_16(const __lambda_6_16 &) = delete;
// inline __lambda_6_16(__lambda_6_16 &&) noexcept = default;
public: __lambda_6_16(std::unique_ptr<int, std::default_delete<int> > _x)
: x{_x}
{}
};
__lambda_6_16 lambda = __lambda_6_16(__lambda_6_16{std::unique_ptr<int,
std::default_delete<int> >
(std::move(x))});
}
And I believe this last piece of code answers your question. A move occurs, but not [technically] in the constructor.
Captures themselves aren't const, but you can see that the operator() function is. Naturally, if you need to modify the captures, you mark the lambda as mutable.
This question cannot be fully answered in code. You might be able to write somewhat "equivalent" code, but the standard is not specified that way.
With that out of the way, let's dive into [expr.prim.lambda]. The first thing to note is that constructors are only mentioned in [expr.prim.lambda.closure]/13:
The closure type associated with a lambda-expression has no default constructor if the lambda-expression has a lambda-capture and a defaulted default constructor otherwise. It has a defaulted copy constructor and a defaulted move constructor ([class.copy.ctor]). It has a deleted copy assignment operator if the lambda-expression has a lambda-capture and defaulted copy and move assignment operators otherwise ([class.copy.assign]). [ Note: These special member functions are implicitly defined as usual, and might therefore be defined as deleted. — end note ]
So right off the bat, it should be clear that constructors are not formally how capturing objects is defined. You can get pretty close (see the cppinsights.io answer), but the details differ (note how the code in that answer for case 4 does not compile).
These are the main standard clauses needed to discuss case 1:
[expr.prim.lambda.capture]/10
[...]
For each entity captured by copy, an unnamed non-static data member is declared in the closure type.
The declaration order of these members is unspecified.
The type of such a data member is the referenced type if the entity is a reference to an object, an lvalue reference to the referenced function type if the entity is a reference to a function, or the type of the corresponding captured entity otherwise.
A member of an anonymous union shall not be captured by copy.
[expr.prim.lambda.capture]/11
Every id-expression within the compound-statement of a lambda-expression that is an odr-use of an entity captured by copy is transformed into an access to the corresponding unnamed data member of the closure type. [...]
[expr.prim.lambda.capture]/15
When the lambda-expression is evaluated, the entities that are captured by copy are used to direct-initialize each corresponding non-static data member of the resulting closure object, and the non-static data members corresponding to the init-captures are initialized as indicated by the corresponding initializer (which may be copy- or direct-initialization). [...]
Let's apply this to your case 1:
Case 1: capture by value / default capture by value
int x = 6;
auto lambda = [x]() { std::cout << x << std::endl; };
The closure type of this lambda will have an unnamed non-static data member (let's call it __x) of type int (since x is neither a reference nor a function), and accesses to x within the lambda body are transformed to accesses to __x. When we evaluate the lambda expression (i.e. when assigning to lambda), we direct-initialize __x with x.
In short, only one copy takes place. The constructor of the closure type is not involved, and it is not possible to express this in "normal" C++ (note that the closure type is not an aggregate type either).
Reference capture involve [expr.prim.lambda.capture]/12:
An entity is captured by reference if it is implicitly or explicitly captured but not captured by copy. It is unspecified whether additional unnamed non-static data members are declared in the closure type for entities captured by reference. [...]
There is another paragraph about reference capture of references but we're not doing that anywhere.
So, for case 2:
Case 2: capture by reference / default capture by reference
int x = 6;
auto lambda = [&x]() { std::cout << x << std::endl; };
We don't know whether a member is added to the closure type. x in the lambda body might just directly refer to the x outside. This is up to the compiler to figure out, and it will do this in some form of intermediate language (which differs from compiler to compiler), not a source transformation of the C++ code.
Init captures are detailed in [expr.prim.lambda.capture]/6:
An init-capture behaves as if it declares and explicitly captures a variable of the form auto init-capture ; whose declarative region is the lambda-expression's compound-statement, except that:
(6.1)
if the capture is by copy (see below), the non-static data member declared for the capture and the variable are treated as two different ways of referring to the same object, which has the lifetime of the non-static data member, and no additional copy and destruction is performed, and
(6.2)
if the capture is by reference, the variable's lifetime ends when the closure object's lifetime ends.
Given that, let's look at case 3:
Case 3: Generalised init capture
auto lambda = [x = 33]() { std::cout << x << std::endl; };
As stated, imagine this as a variable being created by auto x = 33; and explicitly captured by copy. This variable is only "visible" within the lambda body. As noted in [expr.prim.lambda.capture]/15 earlier, the initialization of the corresponding member of the closure type (__x for posterity) is by the given initializer upon evaluation of the lambda expression.
For the avoidance of doubt: This does not mean things are initialized twice here. The auto x = 33; is an "as if" to inherit the semantics of simple captures, and the described initialization is a modification to those semantics. Only one initialization happens.
This also covers case 4:
auto l = [p = std::move(unique_ptr_var)]() {
// do something with unique_ptr_var
};
The closure type member is initialized by __p = std::move(unique_ptr_var) when the lambda expression is evaluated (i.e. when l is assigned to). Accesses to p in the lambda body are transformed into accesses to __p.
TL;DR: Only the minimal number of copies/initializations/moves are performed (as one would hope/expect). I would assume that lambdas are not specified in terms of a source transformation (unlike other syntactic sugar) exactly because expressing things in terms of constructors would necessitate superfluous operations.
I hope this settles the fears expressed in the question :)

Default and zero initialization

#include <string>
 
struct T1 { int mem; };
 
struct T2
{
int mem;
T2() { } // "mem" is not in the initializer list
};
 
int n; // static non-class, a two-phase initialization is done:
// 1) zero initialization initializes n to zero
// 2) default initialization does nothing, leaving n being zero
 
int main()
{
int n; // non-class, the value is indeterminate
std::string s; // class, calls default ctor, the value is "" (empty string)
std::string a[2]; // array, default-initializes the elements, the value is {"", ""}
// int& r; // error: a reference
// const int n; // error: a const non-class
// const T1 t1; // error: const class with implicit default ctor
T1 t1; // class, calls implicit default ctor
const T2 t2; // const class, calls the user-provided default ctor
// t2.mem is default-initialized (to indeterminate value)
}
Im currently looking at the reference guide, however there a a few things i don't understand.
I have run the above code, for the struct T2, the data member "int mem" is not in the initialiser list. It is said that t2.mem is default-initialized to an indeterminate value.
But when i run this code, t2.mem seems to be zero initialised for me?
But when i run this code, t2.mem seems to be zero initialised for me?
No, in both cases (T1, T2), mem is not initialized, or initialize by indeterminate value. The explicit declaration of the constructor in this case does not affect the mem initialization. If you want to initialize mem, you must do it explicitly in the member initializer list:
struct T2
{
int mem;
T2() : mem(0) { }
};
Or through the default member initializer:
struct T1 { int mem = 0; };
Or through aggregate initialization ofT1, which is only takes place if T1 does not have any user declared constructors.
struct T1 { int mem; }
int main() {
T1 a{}; // aggregate initialization
assert(a.mem == 0)
T1 b; // default constructor does not value initialize mem
}
If you see that mem initialized by 0 in the second case, then this is most likely a feature of the compiler, or you just get lucky with the 0 value. This is not guaranteed by the standard, don't rely on it.

Are local class rules aligned to c++14 return type deduction?

While reading this part of C++14 (a free draft N4141, closest to C++14):
9.8 Local class declarations [class.local]
[..]
The name of a local class is local to its enclosing scope. [..]
Declarations in a local class shall not odr-use (3.2) a variable with
automatic storage duration from an enclosing scope. [ Example:
//[..]
void f()
{
static int s ;
int x;
// [..]
struct local {
int g() { return x; } // error: odr-use of automatic variable x
int h() { return s; } // OK
// [..]
};
}
local* p = 0; // error: local not in scope
—end example ]
I noticed, that, first - I can define p with return value auto deduction:
auto f()
{
static int s;
int x;
struct local
{
int h() { return s; }
};
return local{};
}
decltype(f())* p = 0; // OK - ignored, that local is not in scope!
Maybe it seems ok, why not to use local type by deducing it from function return value, but - it seems in this way I can access local s variable before it is constructed:
struct TalkativeInt
{
TalkativeInt() : value()
{
std::cout << "TalkativeInt()\n";
}
TalkativeInt(int value) : value(value)
{
std::cout << "TalkativeInt(" << value << ")\n";
}
int value;
};
auto f()
{
static TalkativeInt s = 7;
int x;
struct local
{
auto h() { return s.value; }
};
return local{};
}
decltype(f())* p = 0;
int main() {
decltype(f()) l;
std::cout << l.h();
}
Output is just::
0
But one might expect:
TalkativeInt(7)
7
Both clang and gcc do not protest in any way, see demo.
I wonder, maybe such case should be mentioned somehow either in
9.8 Local class declarations [class.local]
or in
7.1.6.4 auto specifier [dcl.spec.auto]
?
Of course, I feel that reading (and writing) from variable before it is constructed is bad thing, but I have not found anything about that in standard - probably, it was not possible before C++14? Or there are some basic rules I just overlooked?
The rule for static local variables is plain and simple:
Dynamic initialization of a block-scope variable with static storage
duration (3.7.1) or thread storage duration (3.7.2) is performed the
first time control passes through its declaration; such a variable is
considered initialized upon the completion of its initialization.
And accessing an object's members before its construction is disallowed by [basic.life]/(7.1).