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.

Revisions

  1. JoshHayes created this gist Sep 13, 2023.
    102 changes: 102 additions & 0 deletions concepts_for_composition_over_inheritance.cpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,102 @@
    // 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;
    }