Skip to content

Commit 039712a

Browse files
committed
Fix deferred init narrowing #1446 by keeping brace-init in caller context.
Use construct_from(lambda) so constexpr initializers are not lost across a call boundary, matching immediate initialization behavior. Note, change out::construct to use brace-init in all paths to be consistent and to align with deferred init. Add regression tests for literals (negative too), constexpr, aggregates. Update snapshots affected by the change.
1 parent 94ee867 commit 039712a

43 files changed

Lines changed: 950 additions & 164 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

include/cpp2util.h

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1488,11 +1488,15 @@ class deferred_init {
14881488
auto destroy() -> void { if (init) { t().~T(); } init = false; }
14891489

14901490
public:
1491+
using value_type = T;
1492+
14911493
constexpr deferred_init() noexcept { }
14921494
constexpr ~deferred_init() noexcept { destroy(); }
14931495
constexpr auto value() noexcept -> T& { cpp2_default.enforce(init); return t(); }
14941496

14951497
constexpr auto construct(auto&& ...args) -> void { cpp2_default.enforce(!init); new (&data) T{CPP2_FORWARD(args)...}; init = true; }
1498+
1499+
constexpr auto construct_from(auto&& factory) -> void { cpp2_default.enforce(!init); new (&data) T(CPP2_FORWARD(factory)()); init = true; }
14961500
};
14971501

14981502

@@ -1512,6 +1516,8 @@ class out {
15121516
bool called_construct_ = false;
15131517

15141518
public:
1519+
using value_type = T;
1520+
15151521
constexpr out(T* t_) noexcept : t{ t_}, has_t{true} { cpp2_default.enforce( t); }
15161522
constexpr out(deferred_init<T>* dt_) noexcept : dt{dt_}, has_t{false} { cpp2_default.enforce(dt); }
15171523
constexpr out(out<T>* ot_) noexcept : ot{ot_}, has_t{ot_->has_t} { cpp2_default.enforce(ot);
@@ -1535,10 +1541,14 @@ class out {
15351541
}
15361542

15371543
constexpr auto construct(auto&& ...args) -> void {
1544+
construct_from([&]() -> T { return T{CPP2_FORWARD(args)...}; });
1545+
}
1546+
1547+
constexpr auto construct_from(auto&& factory) -> void {
15381548
if (has_t || called_construct()) {
1539-
if constexpr (requires { *t = T(CPP2_FORWARD(args)...); }) {
1549+
if constexpr (requires { *t = CPP2_FORWARD(factory)(); }) {
15401550
cpp2_default.enforce( t );
1541-
*t = T(CPP2_FORWARD(args)...);
1551+
*t = CPP2_FORWARD(factory)();
15421552
}
15431553
else {
15441554
cpp2_default.report_violation("attempted to copy assign, but copy assignment is not available");
@@ -1547,15 +1557,15 @@ class out {
15471557
else {
15481558
cpp2_default.enforce( dt );
15491559
if (dt->init) {
1550-
if constexpr (requires { *t = T(CPP2_FORWARD(args)...); }) {
1551-
dt->value() = T(CPP2_FORWARD(args)...);
1560+
if constexpr (requires { dt->value() = CPP2_FORWARD(factory)(); }) {
1561+
dt->value() = CPP2_FORWARD(factory)();
15521562
}
15531563
else {
15541564
cpp2_default.report_violation("attempted to copy assign, but copy assignment is not available");
15551565
}
15561566
}
15571567
else {
1558-
dt->construct(CPP2_FORWARD(args)...);
1568+
dt->construct_from(CPP2_FORWARD(factory));
15591569
called_construct() = true;
15601570
}
15611571
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#include <cstdint>
2+
3+
struct S {
4+
std::int8_t x;
5+
std::int8_t y;
6+
};
7+
8+
struct Nested {
9+
S left;
10+
S right;
11+
};
12+
13+
inline constexpr std::int16_t constexpr_i16 = 100;
14+
inline constexpr std::int8_t constexpr_i8 = 8;
15+
16+
main: () -> int = {
17+
runtime_i8: i8 = 8;
18+
19+
s_good0: S = (constexpr_i16, runtime_i8);
20+
s_good: S;
21+
s_good = (constexpr_i16, runtime_i8);
22+
23+
s_reverse0: S = (runtime_i8, constexpr_i16);
24+
s_reverse: S;
25+
s_reverse = (runtime_i8, constexpr_i16);
26+
27+
s_constexpr0: S = (constexpr_i16, constexpr_i16);
28+
s_constexpr: S;
29+
s_constexpr = (constexpr_i16, constexpr_i16);
30+
31+
s_runtime0: S = (runtime_i8, runtime_i8);
32+
s_runtime: S;
33+
s_runtime = (runtime_i8, runtime_i8);
34+
35+
nested0: Nested = (
36+
:S = (constexpr_i16, runtime_i8),
37+
:S = (constexpr_i8, runtime_i8)
38+
);
39+
nested: Nested;
40+
nested = (
41+
:S = (constexpr_i16, runtime_i8),
42+
:S = (constexpr_i8, runtime_i8)
43+
);
44+
45+
assert(s_good0.x == 100 && s_good.y == 8);
46+
assert(s_reverse0.x == 8 && s_reverse.y == 100);
47+
assert(s_constexpr0.x == 100 && s_constexpr.y == 100);
48+
assert(s_runtime0.x == 8 && s_runtime.y == 8);
49+
assert(nested0.left.x == 100 && nested.right.y == 8);
50+
51+
return 0;
52+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#include <cstdint>
2+
#include <array>
3+
4+
struct S {
5+
std::int8_t x;
6+
std::int8_t y;
7+
};
8+
9+
struct Nested {
10+
S left;
11+
S right;
12+
};
13+
14+
inline constexpr std::int16_t constexpr_i16 = 100;
15+
16+
fill_i8: (out x: i8) = {
17+
x = 42;
18+
}
19+
20+
fill_array: (out a: std::array<i8, 3>) = {
21+
a = (1, 2, 3);
22+
}
23+
24+
fill_s: (out s: S, x: i8) = {
25+
s = (constexpr_i16, x);
26+
}
27+
28+
fill_nested: (out n: Nested, x: i8) = {
29+
n = (
30+
:S = (constexpr_i16, x),
31+
:S = (x, constexpr_i16)
32+
);
33+
}
34+
35+
// out -> out forwarding still keeps brace-init in the final writer
36+
fill_via_fwd: (out x: i8) = {
37+
fill_i8(out x);
38+
}
39+
40+
// first assignment constructs; second goes through the assign path
41+
refill_s: (out s: S, x: i8) = {
42+
s = (x, constexpr_i16);
43+
s = (constexpr_i16, x);
44+
}
45+
46+
overwrite_i8: (out x: i8) = {
47+
x = 100;
48+
}
49+
50+
main: () -> int = {
51+
a: i8;
52+
fill_i8(out a);
53+
54+
arr: std::array<i8, 3>;
55+
fill_array(out arr);
56+
57+
runtime_i8: i8 = 8;
58+
59+
s: S;
60+
fill_s(out s, runtime_i8);
61+
62+
n: Nested;
63+
fill_nested(out n, runtime_i8);
64+
65+
b: i8;
66+
fill_via_fwd(out b);
67+
68+
s2: S;
69+
refill_s(out s2, runtime_i8);
70+
71+
// already-initialized target: out assigns rather than constructs
72+
c: i8 = 1;
73+
overwrite_i8(out c);
74+
75+
assert(a == 42);
76+
assert(arr[0] == 1 && arr[1] == 2 && arr[2] == 3);
77+
assert(s.x == 100 && s.y == 8);
78+
assert(n.left.x == 100 && n.left.y == 8);
79+
assert(n.right.x == 8 && n.right.y == 100);
80+
assert(b == 42);
81+
assert(s2.x == 100 && s2.y == 8);
82+
assert(c == 100);
83+
84+
return 0;
85+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
value: i32 == 20;
2+
3+
foo: () -> i32 == 20;
4+
5+
add: (x: i32, y: i32) -> i32 == x + y;
6+
7+
main: () -> int = {
8+
a0: i8 = value + 100;
9+
a: i8;
10+
a = value + 100;
11+
12+
b0: i8 = foo() + 100;
13+
b: i8;
14+
b = foo() + 100;
15+
16+
c0: i16 = add(value, foo()) + 100;
17+
c: i16;
18+
c = add(value, foo()) + 100;
19+
20+
local: i32 == 10;
21+
22+
d0: i16 = add(local, foo()) * 2 + value;
23+
d: i16;
24+
d = add(local, foo()) * 2 + value;
25+
26+
e0: std::array<i8, 3> = (value + 100, foo() + 100, add(local, foo()));
27+
e: std::array<i8, 3>;
28+
e = (value + 100, foo() + 100, add(local, foo()));
29+
30+
return 0;
31+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
main: () -> int = {
2+
a0: u8 = 'a';
3+
a: u8;
4+
a = 'a';
5+
6+
b0: i8 = 1;
7+
b: i8;
8+
b = 1;
9+
10+
c0: u8 = 2;
11+
c: u8;
12+
c = 2;
13+
14+
d0: i16 = 3;
15+
d: i16;
16+
d = 3;
17+
18+
e0: u16 = 4;
19+
e: u16;
20+
e = 4;
21+
22+
f0: std::array<i8, 3> = (5, 6, 7);
23+
f: std::array<i8, 3>;
24+
f = (5, 6, 7);
25+
26+
return 0;
27+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Target type is {i8, i8}. We try every pair from:
2+
// ce_i8, ce_i16, rt_i8, rt_i16
3+
// = 4 x 4 = 16 combinations.
4+
//
5+
// Constexpr sources must appear by name in the initializer, otherwise they
6+
// stop being constant expressions and every i16 case would fail.
7+
8+
ce_i8: i8 == 8;
9+
ce_i16: i16 == 8;
10+
11+
try_all_combinations: (rt_i8: i8, rt_i16: i16) = {
12+
a00: std::array<i8, 2>;
13+
a00 = (ce_i8, ce_i8);
14+
a01: std::array<i8, 2>;
15+
a01 = (ce_i8, ce_i16);
16+
a02: std::array<i8, 2>;
17+
a02 = (ce_i8, rt_i8);
18+
a03: std::array<i8, 2>;
19+
a03 = (ce_i8, rt_i16); // fail: runtime i16 -> i8
20+
21+
a10: std::array<i8, 2>;
22+
a10 = (ce_i16, ce_i8);
23+
a11: std::array<i8, 2>;
24+
a11 = (ce_i16, ce_i16);
25+
a12: std::array<i8, 2>;
26+
a12 = (ce_i16, rt_i8);
27+
a13: std::array<i8, 2>;
28+
a13 = (ce_i16, rt_i16); // fail: runtime i16 -> i8
29+
30+
a20: std::array<i8, 2>;
31+
a20 = (rt_i8, ce_i8);
32+
a21: std::array<i8, 2>;
33+
a21 = (rt_i8, ce_i16);
34+
a22: std::array<i8, 2>;
35+
a22 = (rt_i8, rt_i8);
36+
a23: std::array<i8, 2>;
37+
a23 = (rt_i8, rt_i16); // fail: runtime i16 -> i8
38+
39+
a30: std::array<i8, 2>;
40+
a30 = (rt_i16, ce_i8); // fail: runtime i16 -> i8
41+
a31: std::array<i8, 2>;
42+
a31 = (rt_i16, ce_i16); // fail: runtime i16 -> i8
43+
a32: std::array<i8, 2>;
44+
a32 = (rt_i16, rt_i8); // fail: runtime i16 -> i8
45+
a33: std::array<i8, 2>;
46+
a33 = (rt_i16, rt_i16); // fail: runtime i16 -> i8
47+
}
48+
49+
main: () -> int = {
50+
rt_i8: i8 = 8;
51+
rt_i16: i16 = 8;
52+
try_all_combinations(rt_i8, rt_i16);
53+
return 0;
54+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
main: () -> int = {
2+
a0: i8 = -1;
3+
a: i8;
4+
a = -1;
5+
6+
b0: i16 = -2;
7+
b: i16;
8+
b = -2;
9+
10+
c0: std::array<i8, 3> = (-3, -4, -5);
11+
c: std::array<i8, 3>;
12+
c = (-3, -4, -5);
13+
14+
return 0;
15+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:19:125: error: non-constant-expression cannot be narrowed from type 'cpp2::impl::in<cpp2::i16>' (aka 'const short') to 'signed char' in initializer list [-Wc++11-narrowing]
2+
19 | a03.construct_from([&]() -> typename CPP2_TYPEOF(a03)::value_type { return typename CPP2_TYPEOF(a03)::value_type{ce_i8, rt_i16}; });// fail: runtime i16 -> i8
3+
| ^~~~~~
4+
pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:19:125: note: insert an explicit cast to silence this issue
5+
19 | a03.construct_from([&]() -> typename CPP2_TYPEOF(a03)::value_type { return typename CPP2_TYPEOF(a03)::value_type{ce_i8, rt_i16}; });// fail: runtime i16 -> i8
6+
| ^~~~~~
7+
| static_cast<signed char>( )
8+
pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:28:126: error: non-constant-expression cannot be narrowed from type 'cpp2::impl::in<cpp2::i16>' (aka 'const short') to 'signed char' in initializer list [-Wc++11-narrowing]
9+
28 | a13.construct_from([&]() -> typename CPP2_TYPEOF(a13)::value_type { return typename CPP2_TYPEOF(a13)::value_type{ce_i16, rt_i16}; });// fail: runtime i16 -> i8
10+
| ^~~~~~
11+
pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:28:126: note: insert an explicit cast to silence this issue
12+
28 | a13.construct_from([&]() -> typename CPP2_TYPEOF(a13)::value_type { return typename CPP2_TYPEOF(a13)::value_type{ce_i16, rt_i16}; });// fail: runtime i16 -> i8
13+
| ^~~~~~
14+
| static_cast<signed char>( )
15+
pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:37:125: error: non-constant-expression cannot be narrowed from type 'cpp2::impl::in<cpp2::i16>' (aka 'const short') to 'signed char' in initializer list [-Wc++11-narrowing]
16+
37 | a23.construct_from([&]() -> typename CPP2_TYPEOF(a23)::value_type { return typename CPP2_TYPEOF(a23)::value_type{rt_i8, rt_i16}; });// fail: runtime i16 -> i8
17+
| ^~~~~~
18+
pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:37:125: note: insert an explicit cast to silence this issue
19+
37 | a23.construct_from([&]() -> typename CPP2_TYPEOF(a23)::value_type { return typename CPP2_TYPEOF(a23)::value_type{rt_i8, rt_i16}; });// fail: runtime i16 -> i8
20+
| ^~~~~~
21+
| static_cast<signed char>( )
22+
pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:40:118: error: non-constant-expression cannot be narrowed from type 'cpp2::impl::in<cpp2::i16>' (aka 'const short') to 'signed char' in initializer list [-Wc++11-narrowing]
23+
40 | a30.construct_from([&]() -> typename CPP2_TYPEOF(a30)::value_type { return typename CPP2_TYPEOF(a30)::value_type{rt_i16, ce_i8}; });// fail: runtime i16 -> i8
24+
| ^~~~~~
25+
pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:40:118: note: insert an explicit cast to silence this issue
26+
40 | a30.construct_from([&]() -> typename CPP2_TYPEOF(a30)::value_type { return typename CPP2_TYPEOF(a30)::value_type{rt_i16, ce_i8}; });// fail: runtime i16 -> i8
27+
| ^~~~~~
28+
| static_cast<signed char>( )
29+
pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:42:118: error: non-constant-expression cannot be narrowed from type 'cpp2::impl::in<cpp2::i16>' (aka 'const short') to 'signed char' in initializer list [-Wc++11-narrowing]
30+
42 | a31.construct_from([&]() -> typename CPP2_TYPEOF(a31)::value_type { return typename CPP2_TYPEOF(a31)::value_type{rt_i16, ce_i16}; });// fail: runtime i16 -> i8
31+
| ^~~~~~
32+
pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:42:118: note: insert an explicit cast to silence this issue
33+
42 | a31.construct_from([&]() -> typename CPP2_TYPEOF(a31)::value_type { return typename CPP2_TYPEOF(a31)::value_type{rt_i16, ce_i16}; });// fail: runtime i16 -> i8
34+
| ^~~~~~
35+
| static_cast<signed char>( )
36+
pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:44:118: error: non-constant-expression cannot be narrowed from type 'cpp2::impl::in<cpp2::i16>' (aka 'const short') to 'signed char' in initializer list [-Wc++11-narrowing]
37+
44 | a32.construct_from([&]() -> typename CPP2_TYPEOF(a32)::value_type { return typename CPP2_TYPEOF(a32)::value_type{rt_i16, rt_i8}; });// fail: runtime i16 -> i8
38+
| ^~~~~~
39+
pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:44:118: note: insert an explicit cast to silence this issue
40+
44 | a32.construct_from([&]() -> typename CPP2_TYPEOF(a32)::value_type { return typename CPP2_TYPEOF(a32)::value_type{rt_i16, rt_i8}; });// fail: runtime i16 -> i8
41+
| ^~~~~~
42+
| static_cast<signed char>( )
43+
pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:46:118: error: non-constant-expression cannot be narrowed from type 'cpp2::impl::in<cpp2::i16>' (aka 'const short') to 'signed char' in initializer list [-Wc++11-narrowing]
44+
46 | a33.construct_from([&]() -> typename CPP2_TYPEOF(a33)::value_type { return typename CPP2_TYPEOF(a33)::value_type{rt_i16, rt_i16}; });// fail: runtime i16 -> i8
45+
| ^~~~~~
46+
pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:46:118: note: insert an explicit cast to silence this issue
47+
46 | a33.construct_from([&]() -> typename CPP2_TYPEOF(a33)::value_type { return typename CPP2_TYPEOF(a33)::value_type{rt_i16, rt_i16}; });// fail: runtime i16 -> i8
48+
| ^~~~~~
49+
| static_cast<signed char>( )
50+
pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:46:126: error: non-constant-expression cannot be narrowed from type 'cpp2::impl::in<cpp2::i16>' (aka 'const short') to 'signed char' in initializer list [-Wc++11-narrowing]
51+
46 | a33.construct_from([&]() -> typename CPP2_TYPEOF(a33)::value_type { return typename CPP2_TYPEOF(a33)::value_type{rt_i16, rt_i16}; });// fail: runtime i16 -> i8
52+
| ^~~~~~
53+
pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:46:126: note: insert an explicit cast to silence this issue
54+
46 | a33.construct_from([&]() -> typename CPP2_TYPEOF(a33)::value_type { return typename CPP2_TYPEOF(a33)::value_type{rt_i16, rt_i16}; });// fail: runtime i16 -> i8
55+
| ^~~~~~
56+
| static_cast<signed char>( )
57+
8 errors generated.

0 commit comments

Comments
 (0)