Skip to content

Commit 0982b8e

Browse files
committed
Add basic support for inheritance
A base type subobject is declared the same as any member object, but with the name `this`. See test case example in this commit, pasted below for convenience Note that `:` continues to be pronounces "is a"... e.g., `f: () -> int` is pronounced as "f is a function returning int," `v: vector<int>` as "v is a vector<int>", `this: Shape` as "this object is a Shape." This is consistent because in Cpp2 I'm pursuing the experiment of making inheritance be always `public` and therefore inheritance should always model IS-A substitutability. - Why not protected inheritance? Because the only theoretical use of that I know of is to enable IS-A substitutability only for code within the same class hierarchy, and I know of no actual use of that feature. - Why not private inheritance? Because it is anti-recommended, nearly always that should be a private data member instead, and the main reason to use a private base is to make that data member outlive a base class object... which is already covered in Cpp2 because all subobjects (bases and members) can be declared in any order. In this initial checkin, if there are any non-base subobjects (ordinary data members) that are declared before base subobjects (base classes), as an implementation detail those non-bases are emitted as private base classes via a helper wrapper that prevents their interface (functions) from leaking into the type. If a type has base classes, I don't generate assignment from construction. This is because polymorphic types usually don't want (nonvirtual) assignment, and so base classes generally don't provide assignment so a generated memberwise assignment wouldn't work anyway. However, as of this commit, I don't currently ban a user explicitly writing their own assignment operator if they want to, which will be fine as long as `Base::operator=` memberwise calls are available. Abstract virtual functions are simply virtual functions that have no initializer. (All other functions in Cpp2 must have an initializer.) Also corrected the namespace alias representation to use id-expression ### Test case (pasted for convenience) ``` Human: type = { operator=: (out this) = {} speak: (virtual this); } N: namespace = { Machine: type = { operator=: (out this, id: std::string) = {} work: (virtual this); } } Cyborg: type = { name: std::string; this: Human = (); this: N::Machine; operator=: (out this, n: std::string) = { name = n; N::Machine = "Acme Corp. engineer tech"; std::cout << "(name)$ checks in for the day's shift\n"; } speak: (override this) = std::cout << "(name)$ cracks a few jokes with a coworker\n"; work: (override this) = std::cout << "(name)$ carries some half-tonne crates of Fe2O3 to cold storage\n"; operator=: (move this) = std::cout << "Tired but satisfied after another successful day, (name)$ checks out and goes home to their family\n"; } make_speak: ( h: Human ) = { std::cout << "-> [vcall: make_speak] "; h.speak(); } do_work: ( m: N::Machine ) = { std::cout << "-> [vcall: do_work] "; m.work(); } main: () = { c: Cyborg = "Parsnip"; c.make_speak(); c.do_work(); } ``` Output: ``` Parsnip checks in for the day's shift -> [vcall: make_speak] Parsnip cracks a few jokes with a coworker -> [vcall: do_work] Parsnip carries some half-tonne crates of Fe2O3 to cold storage Tired but satisfied after another successful day, Parsnip checks out and goes home to their family ```
1 parent 827ed79 commit 0982b8e

17 files changed

+714
-177
lines changed

include/cpp2util.h

+59-46
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ auto assert_not_null(auto&& p CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> declty
363363
// doesn't guarantee that using == and != will reliably report whether an
364364
// STL iterator has the default-constructed value
365365
Null.expects(p != CPP2_TYPEOF(p){}, "dynamic null dereference attempt detected" CPP2_SOURCE_LOCATION_ARG);
366-
return std::forward<decltype(p)>(p);
366+
return CPP2_FORWARD(p);
367367
}
368368

369369
// Subscript bounds checking
@@ -373,14 +373,14 @@ auto assert_in_bounds(auto&& x, auto&& arg CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAU
373373
requires { std::ssize(x); x[arg]; })
374374
{
375375
Bounds.expects(0 <= arg && arg < std::ssize(x), "out of bounds access attempt detected" CPP2_SOURCE_LOCATION_ARG);
376-
return std::forward<decltype(x)>(x) [ std::forward<decltype(arg)>(arg) ];
376+
return CPP2_FORWARD(x) [ CPP2_FORWARD(arg) ];
377377
}
378378

379379
auto assert_in_bounds(auto&& x, auto&& arg CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> decltype(auto)
380380
requires (!(std::is_integral_v<CPP2_TYPEOF(arg)> &&
381381
requires { std::ssize(x); x[arg]; }))
382382
{
383-
return std::forward<decltype(x)>(x) [ std::forward<decltype(arg)>(arg) ];
383+
return CPP2_FORWARD(x) [ CPP2_FORWARD(arg) ];
384384
}
385385

386386

@@ -468,20 +468,20 @@ auto Typeid() -> decltype(auto) {
468468
struct {
469469
template<typename T>
470470
[[nodiscard]] auto cpp2_new(auto&& ...args) const -> std::unique_ptr<T> {
471-
return std::make_unique<T>(std::forward<decltype(args)>(args)...);
471+
return std::make_unique<T>(CPP2_FORWARD(args)...);
472472
}
473473
} unique;
474474

475475
[[maybe_unused]] struct {
476476
template<typename T>
477477
[[nodiscard]] auto cpp2_new(auto&& ...args) const -> std::shared_ptr<T> {
478-
return std::make_shared<T>(std::forward<decltype(args)>(args)...);
478+
return std::make_shared<T>(CPP2_FORWARD(args)...);
479479
}
480480
} shared;
481481

482482
template<typename T>
483483
[[nodiscard]] auto cpp2_new(auto&& ...args) -> std::unique_ptr<T> {
484-
return unique.cpp2_new<T>(std::forward<decltype(args)>(args)...);
484+
return unique.cpp2_new<T>(CPP2_FORWARD(args)...);
485485
}
486486

487487

@@ -504,10 +504,12 @@ using in =
504504
//
505505
// Initialization: These are closely related...
506506
//
507-
// deferred_init<T> For deferred-initialized local or member variable
507+
// deferred_init<T> For deferred-initialized local object
508508
//
509509
// out<T> For out parameter
510510
//
511+
// store_as_base<T> For member object declared before a base object
512+
//
511513
//-----------------------------------------------------------------------
512514
//
513515
template<typename T>
@@ -527,10 +529,11 @@ class deferred_init {
527529
~deferred_init() noexcept { destroy(); }
528530
auto value() noexcept -> T& { Default.expects(init); return t(); }
529531

530-
auto construct (auto&& ...args) -> void { Default.expects(!init); new (&data) T(std::forward<decltype(args)>(args)...); init = true; }
531-
auto construct_list(auto&& ...args) -> void { Default.expects(!init); new (&data) T{std::forward<decltype(args)>(args)...}; init = true; }
532+
auto construct (auto&& ...args) -> void { Default.expects(!init); new (&data) T(CPP2_FORWARD(args)...); init = true; }
533+
auto construct_list(auto&& ...args) -> void { Default.expects(!init); new (&data) T{CPP2_FORWARD(args)...}; init = true; }
532534
};
533535

536+
534537
template<typename T>
535538
class out {
536539
// Not going to bother with std::variant here
@@ -572,15 +575,15 @@ class out {
572575
auto construct(auto&& ...args) -> void {
573576
if (has_t || called_construct()) {
574577
Default.expects( t );
575-
*t = T(std::forward<decltype(args)>(args)...);
578+
*t = T(CPP2_FORWARD(args)...);
576579
}
577580
else {
578581
Default.expects( dt );
579582
if (dt->init) {
580-
dt->value() = T(std::forward<decltype(args)>(args)...);
583+
dt->value() = T(CPP2_FORWARD(args)...);
581584
}
582585
else {
583-
dt->construct(std::forward<decltype(args)>(args)...);
586+
dt->construct(CPP2_FORWARD(args)...);
584587
called_construct() = true;
585588
}
586589
}
@@ -589,15 +592,15 @@ class out {
589592
auto construct_list(auto&& ...args) -> void {
590593
if (has_t || called_construct()) {
591594
Default.expects( t );
592-
*t = T{std::forward<decltype(args)>(args)...};
595+
*t = T{CPP2_FORWARD(args)...};
593596
}
594597
else {
595598
Default.expects( dt );
596599
if (dt->init) {
597-
dt->value() = T{std::forward<decltype(args)>(args)...};
600+
dt->value() = T{CPP2_FORWARD(args)...};
598601
}
599602
else {
600-
dt->construct_list(std::forward<decltype(args)>(args)...);
603+
dt->construct_list(CPP2_FORWARD(args)...);
601604
called_construct() = true;
602605
}
603606
}
@@ -616,6 +619,18 @@ class out {
616619
};
617620

618621

622+
template<typename T>
623+
struct store_as_base : private T
624+
{
625+
store_as_base( T const& t ) : T{t} { }
626+
store_as_base( T && t ) : T{std::move(t)} { }
627+
store_as_base( auto && args ) : T{CPP2_FORWARD(args)} { }
628+
629+
auto value__() -> T & { return *this; }
630+
auto value__() const -> T const& { return *this; }
631+
};
632+
633+
619634
//-----------------------------------------------------------------------
620635
//
621636
// CPP2_UFCS: Variadic macro generating a variadic lamba, oh my...
@@ -635,39 +650,39 @@ class out {
635650

636651
#define CPP2_UFCS(FUNCNAME,PARAM1,...) \
637652
[&](auto&& obj, auto&& ...params) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
638-
if constexpr (requires{ std::forward<decltype(obj)>(obj).FUNCNAME(std::forward<decltype(params)>(params)...); }) { \
639-
return std::forward<decltype(obj)>(obj).FUNCNAME(std::forward<decltype(params)>(params)...); \
653+
if constexpr (requires{ CPP2_FORWARD(obj).FUNCNAME(CPP2_FORWARD(params)...); }) { \
654+
return CPP2_FORWARD(obj).FUNCNAME(CPP2_FORWARD(params)...); \
640655
} else { \
641-
return FUNCNAME(std::forward<decltype(obj)>(obj), std::forward<decltype(params)>(params)...); \
656+
return FUNCNAME(CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \
642657
} \
643658
}(PARAM1, __VA_ARGS__)
644659

645660
#define CPP2_UFCS_0(FUNCNAME,PARAM1) \
646661
[&](auto&& obj) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
647-
if constexpr (requires{ std::forward<decltype(obj)>(obj).FUNCNAME(); }) { \
648-
return std::forward<decltype(obj)>(obj).FUNCNAME(); \
662+
if constexpr (requires{ CPP2_FORWARD(obj).FUNCNAME(); }) { \
663+
return CPP2_FORWARD(obj).FUNCNAME(); \
649664
} else { \
650-
return FUNCNAME(std::forward<decltype(obj)>(obj)); \
665+
return FUNCNAME(CPP2_FORWARD(obj)); \
651666
} \
652667
}(PARAM1)
653668

654669
#define CPP2_UFCS_REMPARENS(...) __VA_ARGS__
655670

656671
#define CPP2_UFCS_TEMPLATE(FUNCNAME,TEMPARGS,PARAM1,...) \
657672
[&](auto&& obj, auto&& ...params) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
658-
if constexpr (requires{ std::forward<decltype(obj)>(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (std::forward<decltype(params)>(params)...); }) { \
659-
return std::forward<decltype(obj)>(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (std::forward<decltype(params)>(params)...); \
673+
if constexpr (requires{ CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(params)...); }) { \
674+
return CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(params)...); \
660675
} else { \
661-
return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (std::forward<decltype(obj)>(obj), std::forward<decltype(params)>(params)...); \
676+
return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \
662677
} \
663678
}(PARAM1, __VA_ARGS__)
664679

665680
#define CPP2_UFCS_TEMPLATE_0(FUNCNAME,TEMPARGS,PARAM1) \
666681
[&](auto&& obj) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
667-
if constexpr (requires{ std::forward<decltype(obj)>(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); }) { \
668-
return std::forward<decltype(obj)>(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); \
682+
if constexpr (requires{ CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); }) { \
683+
return CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); \
669684
} else { \
670-
return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (std::forward<decltype(obj)>(obj)); \
685+
return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(obj)); \
671686
} \
672687
}(PARAM1)
673688

@@ -676,37 +691,37 @@ class out {
676691

677692
#define CPP2_UFCS_NONLOCAL(FUNCNAME,PARAM1,...) \
678693
[](auto&& obj, auto&& ...params) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
679-
if constexpr (requires{ std::forward<decltype(obj)>(obj).FUNCNAME(std::forward<decltype(params)>(params)...); }) { \
680-
return std::forward<decltype(obj)>(obj).FUNCNAME(std::forward<decltype(params)>(params)...); \
694+
if constexpr (requires{ CPP2_FORWARD(obj).FUNCNAME(CPP2_FORWARD(params)...); }) { \
695+
return CPP2_FORWARD(obj).FUNCNAME(CPP2_FORWARD(params)...); \
681696
} else { \
682-
return FUNCNAME(std::forward<decltype(obj)>(obj), std::forward<decltype(params)>(params)...); \
697+
return FUNCNAME(CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \
683698
} \
684699
}(PARAM1, __VA_ARGS__)
685700

686701
#define CPP2_UFCS_0_NONLOCAL(FUNCNAME,PARAM1) \
687702
[](auto&& obj) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
688-
if constexpr (requires{ std::forward<decltype(obj)>(obj).FUNCNAME(); }) { \
689-
return std::forward<decltype(obj)>(obj).FUNCNAME(); \
703+
if constexpr (requires{ CPP2_FORWARD(obj).FUNCNAME(); }) { \
704+
return CPP2_FORWARD(obj).FUNCNAME(); \
690705
} else { \
691-
return FUNCNAME(std::forward<decltype(obj)>(obj)); \
706+
return FUNCNAME(CPP2_FORWARD(obj)); \
692707
} \
693708
}(PARAM1)
694709

695710
#define CPP2_UFCS_TEMPLATE_NONLOCAL(FUNCNAME,TEMPARGS,PARAM1,...) \
696711
[](auto&& obj, auto&& ...params) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
697-
if constexpr (requires{ std::forward<decltype(obj)>(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (std::forward<decltype(params)>(params)...); }) { \
698-
return std::forward<decltype(obj)>(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (std::forward<decltype(params)>(params)...); \
712+
if constexpr (requires{ CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(params)...); }) { \
713+
return CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(params)...); \
699714
} else { \
700-
return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (std::forward<decltype(obj)>(obj), std::forward<decltype(params)>(params)...); \
715+
return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \
701716
} \
702717
}(PARAM1, __VA_ARGS__)
703718

704719
#define CPP2_UFCS_TEMPLATE_0_NONLOCAL(FUNCNAME,TEMPARGS,PARAM1) \
705720
[](auto&& obj) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
706-
if constexpr (requires{ std::forward<decltype(obj)>(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); }) { \
707-
return std::forward<decltype(obj)>(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); \
721+
if constexpr (requires{ CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); }) { \
722+
return CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); \
708723
} else { \
709-
return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (std::forward<decltype(obj)>(obj)); \
724+
return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(obj)); \
710725
} \
711726
}(PARAM1)
712727

@@ -937,7 +952,7 @@ auto as( X const& x ) -> auto
937952
template< typename C, typename X >
938953
requires std::is_base_of_v<C, X>
939954
auto as( X&& x ) -> C&& {
940-
return std::forward<X>(x);
955+
return CPP2_FORWARD(x);
941956
}
942957

943958
template< typename C, typename X >
@@ -1315,10 +1330,9 @@ class final_action_success
13151330
bool invoke = true;
13161331
};
13171332

1318-
template <class F>
1319-
[[nodiscard]] auto finally_success(F&& f) noexcept
1333+
[[nodiscard]] auto finally_success(auto&& f) noexcept
13201334
{
1321-
return final_action_success<std::remove_cvref_t<F>>{std::forward<F>(f)};
1335+
return final_action_success<CPP2_TYPEOF(f)>{CPP2_FORWARD(f)};
13221336
}
13231337

13241338

@@ -1348,10 +1362,9 @@ class final_action
13481362
bool invoke = true;
13491363
};
13501364

1351-
template <class F>
1352-
[[nodiscard]] auto finally(F&& f) noexcept
1365+
[[nodiscard]] auto finally(auto&& f) noexcept
13531366
{
1354-
return final_action<std::remove_cvref_t<F>>{std::forward<F>(f)};
1367+
return final_action<CPP2_TYPEOF(f)>{CPP2_FORWARD(f)};
13551368
}
13561369

13571370

regression-tests/pure2-types-basics.cpp2

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ myclass : type = {
3939
std::cout << "myclass: destructor\n";
4040
}
4141

42-
f: (virtual this, x: int) = {
42+
f: (this, x: int) = {
4343
std::cout << "N::myclass::f with (x)$\n";
4444
}
4545

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
2+
Human: type = {
3+
operator=: (out this) = {}
4+
speak: (virtual this);
5+
}
6+
7+
N: namespace = {
8+
Machine: type = {
9+
operator=: (out this, id: std::string) = {}
10+
work: (virtual this);
11+
}
12+
}
13+
14+
Cyborg: type = {
15+
name: std::string;
16+
this: Human = ();
17+
this: N::Machine;
18+
19+
operator=: (out this, n: std::string) = {
20+
name = n;
21+
N::Machine = "Acme Corp. engineer tech";
22+
std::cout << "(name)$ checks in for the day's shift\n";
23+
}
24+
25+
speak: (override this) =
26+
std::cout << "(name)$ cracks a few jokes with a coworker\n";
27+
28+
work: (override this) =
29+
std::cout << "(name)$ carries some half-tonne crates of Fe2O3 to cold storage\n";
30+
31+
operator=: (move this) =
32+
std::cout << "Tired but satisfied after another successful day, (name)$ checks out and goes home to their family\n";
33+
}
34+
35+
make_speak: ( h: Human ) = {
36+
std::cout << "-> [vcall: make_speak] ";
37+
h.speak();
38+
}
39+
40+
do_work: ( m: N::Machine ) = {
41+
std::cout << "-> [vcall: do_work] ";
42+
m.work();
43+
}
44+
45+
main: () = {
46+
c: Cyborg = "Parsnip";
47+
c.make_speak();
48+
c.do_work();
49+
}

regression-tests/pure2-types-order-independence-and-nesting.cpp2

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ X: type = {
3434
// X::exx member function description here
3535
exx: (this, count: int) = {
3636
// Exercise '_' anonymous objects too while we're at it
37-
_ := cpp2::finally( :()= std::cout << "leaving call to 'why((count)$)'\n"; );
37+
_ := finally( :()= std::cout << "leaving call to 'why((count)$)'\n"; );
3838
if count < 5 {
3939
py*.why( count+1 ); // use Y object from X
4040
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Parsnip checks in for the day's shift
2+
-> [vcall: make_speak] Parsnip cracks a few jokes with a coworker
3+
-> [vcall: do_work] Parsnip carries some half-tonne crates of Fe2O3 to cold storage
4+
Tired but satisfied after another successful day, Parsnip checks out and goes home to their family

regression-tests/test-results/clang-12/pure2-types-inheritance.cpp.output

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Parsnip checks in for the day's shift
2+
-> [vcall: make_speak] Parsnip cracks a few jokes with a coworker
3+
-> [vcall: do_work] Parsnip carries some half-tonne crates of Fe2O3 to cold storage
4+
Tired but satisfied after another successful day, Parsnip checks out and goes home to their family

regression-tests/test-results/gcc-10/pure2-types-inheritance.cpp.output

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Parsnip checks in for the day's shift
2+
-> [vcall: make_speak] Parsnip cracks a few jokes with a coworker
3+
-> [vcall: do_work] Parsnip carries some half-tonne crates of Fe2O3 to cold storage
4+
Tired but satisfied after another successful day, Parsnip checks out and goes home to their family
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pure2-types-inheritance.cpp

regression-tests/test-results/pure2-types-basics.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class myclass {
4545
public: ~myclass();
4646

4747
#line 42 "pure2-types-basics.cpp2"
48-
public: virtual auto f(cpp2::in<int> x) const -> void;
48+
public: auto f(cpp2::in<int> x) const -> void;
4949

5050
#line 46 "pure2-types-basics.cpp2"
5151
private: int data {42 * 12};

0 commit comments

Comments
 (0)