Created
September 13, 2023 09:10
-
-
Save JoshHayes/8e54a9ae03a47c0157a6737f66880c8c to your computer and use it in GitHub Desktop.
Using C++20 concepts to avoid inheritance - a tiny demo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Using C++20 concepts to enable composition over inheritance - a tiny demo. | |
// Notes from a lovely talk by Jonathan Gopel | |
// | |
// Source: | |
// Using Modern C++ to Eliminate Virtual Functions - Jonathan Gopel - CppCon 2022 | |
// https://www.youtube.com/watch?v=gTNJXVmuRRA | |
// See also: | |
// https://en.cppreference.com/w/cpp/language/constraints | |
#include <concepts> | |
#include <variant> | |
// HasFunc says that foo has a method func that returns a value convertible to int | |
template <typename T> | |
concept HasFunc = requires(T foo) | |
{ | |
{ foo.func() } -> std::same_as<int>; | |
}; | |
// FooA and FooB both satisfy/implement HasFunc | |
struct FooA | |
{ | |
[[nodiscard]] auto func() const -> int | |
{ | |
return 42; | |
} | |
}; | |
struct FooB | |
{ | |
[[nodiscard]] auto func() const -> int | |
{ | |
return 57; | |
} | |
}; | |
// typing is structural, not nominal | |
// there is no `impl HasFunc for Foo` as in Rust | |
// Discussion: https://stackoverflow.com/a/56055342/10707376 | |
static_assert(HasFunc<FooA>); | |
static_assert(HasFunc<FooB>); | |
// this function takes an input that satisfies `HasFunc` | |
auto another_function(HasFunc auto &foo) | |
{ | |
return foo.func() + 123; | |
} | |
// class with member that satisfies `HasFunc` | |
template <HasFunc T> | |
struct Bar | |
{ | |
constexpr Bar(T input_foo) : foo{input_foo} {} | |
constexpr auto set_foo(auto input_foo) -> void | |
{ | |
foo = input_foo; | |
} | |
private: | |
T foo{}; | |
}; | |
// What if we want a class just like Bar that allows us to swap the type? | |
// The solution below from Jonathan Gopel is elegant but incurs the overhead of std::variant | |
template <typename T, typename... Ts> | |
concept same_as_any = (... or std::same_as<T, Ts>); | |
template <HasFunc... Ts> | |
struct BarSwappable | |
{ | |
constexpr BarSwappable(same_as_any<Ts...> auto input_foo) : foo{input_foo} {} | |
constexpr auto set_foo(same_as_any<Ts...> auto input_foo) -> void | |
{ | |
foo = input_foo; | |
} | |
private: | |
std::variant<Ts...> foo{}; | |
}; | |
int main() | |
{ | |
FooA fooA{}; | |
auto outA1 = fooA.func(); | |
auto outA2 = another_function(fooA); | |
FooB fooB{}; | |
auto outB1 = fooB.func(); | |
auto outB2 = another_function(fooB); | |
Bar bar{fooA}; | |
// bar.set_foo(fooB); // fails to compile: Bar initialized with FooA, error: no viable overloaded for '=' | |
// note specification of the full list of types at compile time | |
BarSwappable<FooA, FooB> barS{fooA}; | |
barS.set_foo(fooB); // ok | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment