Skip to content

Instantly share code, notes, and snippets.

@JoshHayes
Created September 13, 2023 09:10
Show Gist options
  • Save JoshHayes/8e54a9ae03a47c0157a6737f66880c8c to your computer and use it in GitHub Desktop.
Save JoshHayes/8e54a9ae03a47c0157a6737f66880c8c to your computer and use it in GitHub Desktop.
Using C++20 concepts to avoid inheritance - a tiny demo
// 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