Skip to content

Commit

Permalink
Add basic support for inheritance
Browse files Browse the repository at this point in the history
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
```
  • Loading branch information
hsutter committed Apr 8, 2023
1 parent 827ed79 commit 0982b8e
Show file tree
Hide file tree
Showing 17 changed files with 714 additions and 177 deletions.
105 changes: 59 additions & 46 deletions include/cpp2util.h
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ auto assert_not_null(auto&& p CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> declty
// doesn't guarantee that using == and != will reliably report whether an
// STL iterator has the default-constructed value
Null.expects(p != CPP2_TYPEOF(p){}, "dynamic null dereference attempt detected" CPP2_SOURCE_LOCATION_ARG);
return std::forward<decltype(p)>(p);
return CPP2_FORWARD(p);
}

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

auto assert_in_bounds(auto&& x, auto&& arg CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> decltype(auto)
requires (!(std::is_integral_v<CPP2_TYPEOF(arg)> &&
requires { std::ssize(x); x[arg]; }))
{
return std::forward<decltype(x)>(x) [ std::forward<decltype(arg)>(arg) ];
return CPP2_FORWARD(x) [ CPP2_FORWARD(arg) ];
}


Expand Down Expand Up @@ -468,20 +468,20 @@ auto Typeid() -> decltype(auto) {
struct {
template<typename T>
[[nodiscard]] auto cpp2_new(auto&& ...args) const -> std::unique_ptr<T> {
return std::make_unique<T>(std::forward<decltype(args)>(args)...);
return std::make_unique<T>(CPP2_FORWARD(args)...);
}
} unique;

[[maybe_unused]] struct {
template<typename T>
[[nodiscard]] auto cpp2_new(auto&& ...args) const -> std::shared_ptr<T> {
return std::make_shared<T>(std::forward<decltype(args)>(args)...);
return std::make_shared<T>(CPP2_FORWARD(args)...);
}
} shared;

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


Expand All @@ -504,10 +504,12 @@ using in =
//
// Initialization: These are closely related...
//
// deferred_init<T> For deferred-initialized local or member variable
// deferred_init<T> For deferred-initialized local object
//
// out<T> For out parameter
//
// store_as_base<T> For member object declared before a base object
//
//-----------------------------------------------------------------------
//
template<typename T>
Expand All @@ -527,10 +529,11 @@ class deferred_init {
~deferred_init() noexcept { destroy(); }
auto value() noexcept -> T& { Default.expects(init); return t(); }

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


template<typename T>
class out {
// Not going to bother with std::variant here
Expand Down Expand Up @@ -572,15 +575,15 @@ class out {
auto construct(auto&& ...args) -> void {
if (has_t || called_construct()) {
Default.expects( t );
*t = T(std::forward<decltype(args)>(args)...);
*t = T(CPP2_FORWARD(args)...);
}
else {
Default.expects( dt );
if (dt->init) {
dt->value() = T(std::forward<decltype(args)>(args)...);
dt->value() = T(CPP2_FORWARD(args)...);
}
else {
dt->construct(std::forward<decltype(args)>(args)...);
dt->construct(CPP2_FORWARD(args)...);
called_construct() = true;
}
}
Expand All @@ -589,15 +592,15 @@ class out {
auto construct_list(auto&& ...args) -> void {
if (has_t || called_construct()) {
Default.expects( t );
*t = T{std::forward<decltype(args)>(args)...};
*t = T{CPP2_FORWARD(args)...};
}
else {
Default.expects( dt );
if (dt->init) {
dt->value() = T{std::forward<decltype(args)>(args)...};
dt->value() = T{CPP2_FORWARD(args)...};
}
else {
dt->construct_list(std::forward<decltype(args)>(args)...);
dt->construct_list(CPP2_FORWARD(args)...);
called_construct() = true;
}
}
Expand All @@ -616,6 +619,18 @@ class out {
};


template<typename T>
struct store_as_base : private T
{
store_as_base( T const& t ) : T{t} { }
store_as_base( T && t ) : T{std::move(t)} { }
store_as_base( auto && args ) : T{CPP2_FORWARD(args)} { }

auto value__() -> T & { return *this; }
auto value__() const -> T const& { return *this; }
};


//-----------------------------------------------------------------------
//
// CPP2_UFCS: Variadic macro generating a variadic lamba, oh my...
Expand All @@ -635,39 +650,39 @@ class out {

#define CPP2_UFCS(FUNCNAME,PARAM1,...) \
[&](auto&& obj, auto&& ...params) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
if constexpr (requires{ std::forward<decltype(obj)>(obj).FUNCNAME(std::forward<decltype(params)>(params)...); }) { \
return std::forward<decltype(obj)>(obj).FUNCNAME(std::forward<decltype(params)>(params)...); \
if constexpr (requires{ CPP2_FORWARD(obj).FUNCNAME(CPP2_FORWARD(params)...); }) { \
return CPP2_FORWARD(obj).FUNCNAME(CPP2_FORWARD(params)...); \
} else { \
return FUNCNAME(std::forward<decltype(obj)>(obj), std::forward<decltype(params)>(params)...); \
return FUNCNAME(CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \
} \
}(PARAM1, __VA_ARGS__)

#define CPP2_UFCS_0(FUNCNAME,PARAM1) \
[&](auto&& obj) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
if constexpr (requires{ std::forward<decltype(obj)>(obj).FUNCNAME(); }) { \
return std::forward<decltype(obj)>(obj).FUNCNAME(); \
if constexpr (requires{ CPP2_FORWARD(obj).FUNCNAME(); }) { \
return CPP2_FORWARD(obj).FUNCNAME(); \
} else { \
return FUNCNAME(std::forward<decltype(obj)>(obj)); \
return FUNCNAME(CPP2_FORWARD(obj)); \
} \
}(PARAM1)

#define CPP2_UFCS_REMPARENS(...) __VA_ARGS__

#define CPP2_UFCS_TEMPLATE(FUNCNAME,TEMPARGS,PARAM1,...) \
[&](auto&& obj, auto&& ...params) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
if constexpr (requires{ std::forward<decltype(obj)>(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (std::forward<decltype(params)>(params)...); }) { \
return std::forward<decltype(obj)>(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (std::forward<decltype(params)>(params)...); \
if constexpr (requires{ CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(params)...); }) { \
return CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(params)...); \
} else { \
return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (std::forward<decltype(obj)>(obj), std::forward<decltype(params)>(params)...); \
return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \
} \
}(PARAM1, __VA_ARGS__)

#define CPP2_UFCS_TEMPLATE_0(FUNCNAME,TEMPARGS,PARAM1) \
[&](auto&& obj) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
if constexpr (requires{ std::forward<decltype(obj)>(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); }) { \
return std::forward<decltype(obj)>(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); \
if constexpr (requires{ CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); }) { \
return CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); \
} else { \
return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (std::forward<decltype(obj)>(obj)); \
return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(obj)); \
} \
}(PARAM1)

Expand All @@ -676,37 +691,37 @@ class out {

#define CPP2_UFCS_NONLOCAL(FUNCNAME,PARAM1,...) \
[](auto&& obj, auto&& ...params) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
if constexpr (requires{ std::forward<decltype(obj)>(obj).FUNCNAME(std::forward<decltype(params)>(params)...); }) { \
return std::forward<decltype(obj)>(obj).FUNCNAME(std::forward<decltype(params)>(params)...); \
if constexpr (requires{ CPP2_FORWARD(obj).FUNCNAME(CPP2_FORWARD(params)...); }) { \
return CPP2_FORWARD(obj).FUNCNAME(CPP2_FORWARD(params)...); \
} else { \
return FUNCNAME(std::forward<decltype(obj)>(obj), std::forward<decltype(params)>(params)...); \
return FUNCNAME(CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \
} \
}(PARAM1, __VA_ARGS__)

#define CPP2_UFCS_0_NONLOCAL(FUNCNAME,PARAM1) \
[](auto&& obj) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
if constexpr (requires{ std::forward<decltype(obj)>(obj).FUNCNAME(); }) { \
return std::forward<decltype(obj)>(obj).FUNCNAME(); \
if constexpr (requires{ CPP2_FORWARD(obj).FUNCNAME(); }) { \
return CPP2_FORWARD(obj).FUNCNAME(); \
} else { \
return FUNCNAME(std::forward<decltype(obj)>(obj)); \
return FUNCNAME(CPP2_FORWARD(obj)); \
} \
}(PARAM1)

#define CPP2_UFCS_TEMPLATE_NONLOCAL(FUNCNAME,TEMPARGS,PARAM1,...) \
[](auto&& obj, auto&& ...params) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
if constexpr (requires{ std::forward<decltype(obj)>(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (std::forward<decltype(params)>(params)...); }) { \
return std::forward<decltype(obj)>(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (std::forward<decltype(params)>(params)...); \
if constexpr (requires{ CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(params)...); }) { \
return CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(params)...); \
} else { \
return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (std::forward<decltype(obj)>(obj), std::forward<decltype(params)>(params)...); \
return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \
} \
}(PARAM1, __VA_ARGS__)

#define CPP2_UFCS_TEMPLATE_0_NONLOCAL(FUNCNAME,TEMPARGS,PARAM1) \
[](auto&& obj) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
if constexpr (requires{ std::forward<decltype(obj)>(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); }) { \
return std::forward<decltype(obj)>(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); \
if constexpr (requires{ CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); }) { \
return CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); \
} else { \
return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (std::forward<decltype(obj)>(obj)); \
return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(obj)); \
} \
}(PARAM1)

Expand Down Expand Up @@ -937,7 +952,7 @@ auto as( X const& x ) -> auto
template< typename C, typename X >
requires std::is_base_of_v<C, X>
auto as( X&& x ) -> C&& {
return std::forward<X>(x);
return CPP2_FORWARD(x);
}

template< typename C, typename X >
Expand Down Expand Up @@ -1315,10 +1330,9 @@ class final_action_success
bool invoke = true;
};

template <class F>
[[nodiscard]] auto finally_success(F&& f) noexcept
[[nodiscard]] auto finally_success(auto&& f) noexcept
{
return final_action_success<std::remove_cvref_t<F>>{std::forward<F>(f)};
return final_action_success<CPP2_TYPEOF(f)>{CPP2_FORWARD(f)};
}


Expand Down Expand Up @@ -1348,10 +1362,9 @@ class final_action
bool invoke = true;
};

template <class F>
[[nodiscard]] auto finally(F&& f) noexcept
[[nodiscard]] auto finally(auto&& f) noexcept
{
return final_action<std::remove_cvref_t<F>>{std::forward<F>(f)};
return final_action<CPP2_TYPEOF(f)>{CPP2_FORWARD(f)};
}


Expand Down
2 changes: 1 addition & 1 deletion regression-tests/pure2-types-basics.cpp2
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ myclass : type = {
std::cout << "myclass: destructor\n";
}

f: (virtual this, x: int) = {
f: (this, x: int) = {
std::cout << "N::myclass::f with (x)$\n";
}

Expand Down
49 changes: 49 additions & 0 deletions regression-tests/pure2-types-inheritance.cpp2
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@

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";

This comment has been minimized.

Copy link
@filipsajdak

filipsajdak Apr 8, 2023

Contributor

It is a little bit inconsistent with the cpp2 syntax ({name}: {type} = {initializer};).

Maybe to keep consistency we should use the following:

this: N::Machine = "Acme Corp. engineer tech";

or

this = :N::Machine = "Acme Corp. engineer tech";

The first one is consistent with the way how the Human part is initialized:

this: Human = ();

This comment has been minimized.

Copy link
@JohelEGP

JohelEGP Apr 8, 2023

Contributor

The colon indicating declaration is already in the data member declaration. Requiring it here too would be inconsistent with non-this data members.

x: type {
  y_var: y_type;
  operator=: (out this) = { y_var = 0; }
}
x: type {
  this: z_type;
  operator=: (out this) = { z_type = 0; }
}

This comment has been minimized.

Copy link
@filipsajdak

filipsajdak Apr 8, 2023

Contributor

Or it is part of the Herb plan from 2016 roadmap diagram:

Unified "." member/scope selection (e.g., std.swap(x,y))

This comment has been minimized.

Copy link
@filipsajdak

filipsajdak Apr 8, 2023

Contributor
name = n;
N::Machine = "Acme Corp. engineer tech";

similar to:

this.name = n;
this.N::Machine = "Acme Corp. engineer tech";

This comment has been minimized.

Copy link
@AbhinavK00

AbhinavK00 Apr 8, 2023

How about:

Cyborg : type is Human, Machine = {  }

Also, that unified member/scope selection, I wonder how will that play down with UFCS?

This comment has been minimized.

Copy link
@JohelEGP

JohelEGP Apr 8, 2023

Contributor

Herb considered that possibility. But I very much prefer that everything looks like a data member. It's simpler.

This comment has been minimized.

Copy link
@AbhinavK00

AbhinavK00 Apr 8, 2023

I feel like that's syntax we could've directly inherited from cpp. Plus, they're not actually data members. Having all the base classes at the top is better plus it's consistent with other languages. With current syntax, they look like special-case members and not base classses.

This comment has been minimized.

Copy link
@msadeqhe

msadeqhe Apr 9, 2023

How is it going to be in generic programming? Consider if we have two base classes with different template arguments:

Cyborg: <T, U> type = {
    ...
    this: N::Machine<T>;
    this: N::Machine<U>;
    ...
}

Are we going to use the following syntax?

N::Machine<T> = "Acme Corp. engineer tech";
N::Machine<U> = "Acme Corp. engineer tech";

So perhaps these are also possible:

local_var := N::Machine<T>;
local_var2 := N::Machine<T>.member;
local_var3 := N::Machine<T>::static_member;
local_var4 := N::Machine<T>(); // operator()

I'm thinking how this will affect my suggestion about Literal Templates 😅.

This comment has been minimized.

Copy link
@hsutter

hsutter Apr 10, 2023

Author Owner

Maybe to keep consistency we should use the following:

this: N::Machine = "Acme Corp. engineer tech";

Yes, that's exactly allowed on the data member definition -- I just happened to write that base without a default initializer in the checked-in version of this test code. (Note your comment is on the initialization =, not on the definition : with optional =. This is what @JohelEGP was getting at above.)

this: N::Machine<T>;
this: N::Machine<U>;

Yes, that should be fine.

Cyborg : type is Human, Machine = {  }

No, I explicitly aim to get rid of this special-case syntax (which by putting bases in a separate list also prevents interleaving base subobjects with other subobjects).

This comment has been minimized.

Copy link
@SebastianTroy

SebastianTroy via email Apr 10, 2023

This comment was marked as off-topic.

This comment has been minimized.

Copy link
@hsutter

hsutter Apr 10, 2023

Author Owner

I might have missed something, but how does the new syntax deal with the dreaded diamond pattern?

I don't currently support virtual inheritance, which is where the diamond would come from. Right now you have full multiple inheritance and so you can still transitively include the same base N times, but you get N subobjects of that type (no diamond) and have to disambiguate which one you want.

I'm waiting to see if it's really necessary to support virtual inheritance. If it is desirable, the extension is easy: I'll just allow writing virtual on a this member, like virtual is already allowed on a this parameter. It'd be ~5 lines of code... so why don't I just add it now? Because I don't want to support the feature (and teach the very weird and subtle complications of using it) if there's insufficient real demand for it anymore... inheritance in general is fine, but we all use it much less than in the 1980s and 1990s, and in my experience at least the use of virtual inheritance was always rare and it has been a long time since I've encountered it at all. So leaving it out and seeing whether anyone notices/complains will generate up-to-date data about real customer demand for the feature... 😁

This comment was marked as off-topic.

Copy link
@JohelEGP

JohelEGP Apr 10, 2023

Contributor

I might have missed something, but how does the new syntax deal with the dreaded diamond pattern?

https://github.com/hsutter/cppfront/wiki/Design-note%3A-Unambiguous-parsing is about that.

Oops... I thought you were referring to the "diamond operator" <>, since the example uses N::Machine<U>.

This comment has been minimized.

Copy link
@SebastianTroy

SebastianTroy via email Apr 10, 2023

This comment has been minimized.

Copy link
@filipsajdak

filipsajdak Apr 10, 2023

Contributor

@SebastianTroy the diamond problem is solved with use of virtual inheritance (so it is all about virtual inheritance that Herb is talking about).

From the same wiki page:

C++ by default follows each inheritance path separately, so a D object would actually contain two separate A objects, and uses of A's members have to be properly qualified. If the inheritance from A to B and the inheritance from A to C are both marked "virtual" (for example, "class B : virtual public A"), C++ takes special care to only create one A object, and uses of A's members work correctly. If virtual inheritance and nonvirtual inheritance are mixed, there is a single virtual A, and a nonvirtual A for each nonvirtual inheritance path to A. C++ requires stating explicitly which parent class the feature to be used is invoked from i.e. Worker::Human.Age. C++ does not support explicit repeated inheritance since there would be no way to qualify which superclass to use (i.e. having a class appear more than once in a single derivation list [class Dog : public Animal, Animal]). C++ also allows a single instance of the multiple class to be created via the virtual inheritance mechanism (i.e. Worker::Human and Musician::Human will reference the same object).

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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ X: type = {
// X::exx member function description here
exx: (this, count: int) = {
// Exercise '_' anonymous objects too while we're at it
_ := cpp2::finally( :()= std::cout << "leaving call to 'why((count)$)'\n"; );
_ := finally( :()= std::cout << "leaving call to 'why((count)$)'\n"; );
if count < 5 {
py*.why( count+1 ); // use Y object from X
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
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
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
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
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pure2-types-inheritance.cpp
2 changes: 1 addition & 1 deletion regression-tests/test-results/pure2-types-basics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class myclass {
public: ~myclass();

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

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

1 comment on commit 0982b8e

@filipsajdak
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am glad to see inheritance in cpp2. I will test it out!

Please sign in to comment.