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.
David Leimbachsays:
Ah but you had a strict! My mistake!
David Leimbachsays:
“Struct”
Willsays:
In the example, iterable_array is defined as a struct, which defaults to public inheritance.
Nathan Myerssays:
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.
cppdevsays:
Memory usage errors become difficult to make when your program has no visible pointers.
Which would kind of take us back to Pascal 🙂
Catsays:
It’s nice seeing someone discovering Alex Stepanov ideas of from 90s.
Helmut Zeiselsays:
Can you please explain that in more detail?
Dmitry Tsitelovsays:
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?
In theory, I suppose it could, but I have not observed this behaviour in the scenario outlined in the blog post.
Mariussays:
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:
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.
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.
Pavelsays:
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.
Should the inheritance not be public? C++ defaults to private inheritance.
Why would that be a concern?
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.
Ah but you had a strict! My mistake!
“Struct”
In the example,
iterable_array
is defined as astruct
, which defaults to public inheritance.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.
Which would kind of take us back to Pascal 🙂
It’s nice seeing someone discovering Alex Stepanov ideas of from 90s.
Can you please explain that in more detail?
If I mark the
count_inheritance
function asinline
and pass to it aniter_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?In theory, I suppose it could, but I have not observed this behaviour in the scenario outlined in the blog post.
Sadly even with
inline
the compilers cannot inline the virtual method calls. Actually, it is even worse (at least with GCC): adding inheritance spoils theconstexpr
, now the compiler needs to make virtual function calls even in the context of templates.I had to add
final
toiterable_arry
to recover optimizations.See the assembly for
concept_count
andinheritance_count
functions with and withoutfinal
:https://godbolt.org/z/6o1v7hEME
taking a concept instance as a parameter
No, no, no. The function template
count
takes a template typeT
parameter which satisfies the conceptis_iterable
. Concept is a set of constraints over types. One cannot really pass a concept as an argument — that’s just too much meta.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.
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.