Daniel Lemire's blog

, 10 min read

Defining interfaces in C++: concepts versus inheritance

16 thoughts on “Defining interfaces in C++: concepts versus inheritance”

  1. David Leimbach says:

    Should the inheritance not be public? C++ defaults to private inheritance.

    1. Why would that be a concern?

      1. David Leimbach says:

        Private inheritance means you don’t get the base class interface as your own in the derived class. It is not an “is a” relation. It’s more of a “has a” relation.

        1. David Leimbach says:

          Ah but you had a strict! My mistake!

          1. David Leimbach says:

            “Struct”

    2. Will says:

      In the example, iterable_array is defined as a struct, which defaults to public inheritance.

  2. Nathan Myers says:

    Moreso: when things are decided at runtime, bugs are not detected without a test that tickles them. With things decided at compile time, you often get to have the compiler refuse to compile the bugs.

    It is a daily occurrence for modern C++ code, as with Rust code, to run correctly on the first try. Memory usage errors become difficult to make when your program has no visible pointers. Concurrency errors become difficult to make when you have no visible threads or thread synchronization.

    1. cppdev says:

      Memory usage errors become difficult to make when your program has no visible pointers.

      Which would kind of take us back to Pascal 🙂

  3. Cat says:

    It’s nice seeing someone discovering Alex Stepanov ideas of from 90s.

    1. Helmut Zeisel says:

      Can you please explain that in more detail?

  4. Dmitry Tsitelov says:

    If I mark the count_inheritance function as inline and pass to it an iter_base instance with a compile-time deducible type, will a modern optimizing compiler do the same (or similar) optimizations as for the concept-based approach?

    1. In theory, I suppose it could, but I have not observed this behaviour in the scenario outlined in the blog post.

    2. Marius says:

      Sadly even with inline the compilers cannot inline the virtual method calls. Actually, it is even worse (at least with GCC): adding inheritance spoils the constexpr , now the compiler needs to make virtual function calls even in the context of templates.

      I had to add final to iterable_arry to recover optimizations.

      See the assembly for concept_count and inheritance_count functions with and without final:

      https://godbolt.org/z/6o1v7hEME

  5. Marius says:

    taking a concept instance as a parameter

    No, no, no. The function template count takes a template type T parameter which satisfies the concept is_iterable. Concept is a set of constraints over types. One cannot really pass a concept as an argument — that’s just too much meta.

  6. You see this sentiment echoed a lot and while in practice, it is mostly true, it’s not because virtualisation is inherently evil, it’s that optimisers are not very good at devirtualisation. In this example, it’s trivial to devirtualise iterable_array with lto or wpo and if it’s marked final it’ll even work across a dynamic boundary.

    Saying that concepts are still simpler and more expressive, easier to use and harder to misuse and in practice generate better code.

  7. Pavel says:

    That passage about “Given an optimizing compiler…” leaves me puzzled. Why would compiler developers go as far as teaching it to reason about programers code to this extent? Optimizing “count(a)” to just returning the size of vector is not a kind of optimization I would like to get under the hood. Because it brings a side effect that can lead to errors in the program. Inner “index” value wouldn’t be changed, while it should.