Re:Defining interfaces in C++: concepts versus inheritance
by bramblerose on Thursday April 20, 2023 @04:07PM
(#35645570)
"Given an optimizing compiler, the first function (count(a)) is likely to just immediately return the size of the backing vector. The function is nearly free."
As far as I can see, neither gcc nor clang actually do this (unless the array has a fixed size; and even then in the gcc case only if the array is zero or one element long).
Probably still more efficient than a virtual call, but also a lot more complex on both the programmer and the compiler, and a lot harder to debug.
Re:Defining interfaces in C++: concepts versus inheritance
by crickey on Thursday April 20, 2023 @04:17PM
(#35645706)
Its only more complex for the programmer because of presumed experiance with polymorphism. Its alot easier to debug a concept because a type either has the function or not there is no overloading or massive inheritance tree where u can easily mistake what is actually being called.
Re:Defining interfaces in C++: concepts versus inheritance
by nemetroid on Thursday April 20, 2023 @04:43PM
(#35646004)
If I remove the inheritance, GCC trunk figures it out at -O2 or above (Clang doesn't figure it out). If I add the inheritance back in, GCC doesn't manage either.
Re:Defining interfaces in C++: concepts versus inheritance
by celrod on Thursday April 20, 2023 @06:11PM
(#35647167)
Clang does figure it out. Note that clang has a branch, not a loop. It jumps to returning 0, else it continues to do the same thing gcc does.
I'd say it figured it out, even if the code isn't as pretty.
I tried using `final` to get them to figure out the inheritance case, but devirtualization didn't work when inlining a method defined on a base class.
https://godbolt.org/z/h4GEYnzMo
Re:Defining interfaces in C++: concepts versus inheritance
by nemetroid on Friday April 21, 2023 @05:43AM
(#35651591)
You're right, I should have read the Clang output more closely. The part about final inheritance makes sense, too, because otherwise a subclass could override the behaviour and count_concept() would have to support that.
Re:Defining interfaces in C++: concepts versus inheritance
by nemetroid on Thursday April 20, 2023 @04:55PM
(#35646146)
Structs default to public inheritance. I don’t think the usage of struct here makes sense though, since there’s an invariant to uphold between the index and the ”std::vector<T> array” (sic).
Re:Defining interfaces in C++: concepts versus inheritance
by meragrin_ on Thursday April 20, 2023 @07:26PM
(#35647911)
No. Straight from the comments:
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.
It's actually something that happens to me fairly often, because I do mix them. I have learned to recognize it, when it happens. The best "cure," is to not use protocol defaults for properties and functions that I want to be implemented by inheritance hierarchies.
Re:Defining interfaces in C++: concepts versus inheritance
by klik99 on Thursday April 20, 2023 @05:26PM
(#35646598)
Concepts seem great, but do they/will they always depend on using templates? Because I try to use templates as a last ditch escape hatch due to debugging/error messages being gnarly.
I'm slowly working my way through the C++ versions (I prefer to use things that are battletested) and mostly loving it except that compiler error messages are just getting more and more esoteric. Or rather, the error messages that have always been esoteric are getting more common since so much of modern C++ features rely on them.
Re:Defining interfaces in C++: concepts versus inheritance
by bobbyi on Thursday April 20, 2023 @05:38PM
(#35646721)
Concepts improve the situation with error messages for templates. You get an error message saying the equivalent of "You can't pass this argument to this template function because type X doesn't match concept Y" rather than the esoteric one you get now.
Re:Defining interfaces in C++: concepts versus inheritance
by Taywee on Thursday April 20, 2023 @05:34PM
(#35646673)
One of the main points of concepts is to make template error messages much better, and for the most part it's true. Concepts error messages still suck, but they tend to suck much less than non-concept template error messages.
Re:Defining interfaces in C++: concepts versus inheritance
by klik99 on Thursday April 20, 2023 @07:29PM
(#35647936)
Thanks! Seriously any suckage reduction no matter how minuscule is a vast improvement over current template errors.
At my rate I'll be on C++20 by 2025 so looking forward to it.
Re:Defining interfaces in C++: concepts versus inheritance
by simplotek on Thursday April 20, 2023 @07:11PM
(#35647769)
> Concepts seem great, but do they/will they always depend on using templates?
I don't get your question. By definition, C++'s concepts are requirements specified on template arguments. Do you foresee any scenario where requirements on template arguments should not be used with templates?
Re:Defining interfaces in C++: concepts versus inheritance
by Taywee on Thursday April 20, 2023 @05:32PM
(#35646658)
I've done some pretty involved concept work. I made this CBOR implementation that heavily uses concepts: https://github.com/absperf/conbor/blob/main/conbor/cbor.hxx (Warning: I abandoned this mid-development. It "worked" for most of my uses, but many things were dropped in mid-development)
I ended up abandoning it because recursive concepts weren't universally functioning (clang didn't support them right), and more nefariously, the C++ name definition rules made a whole lot of things very very painful. concepts were evaluated based on their definition site, not based on names available where they were expanded. This is true of templates to, but in concepts, it prevented me from allowing users to effectively extend my concepts-based library, and some circularly-dependent concepts were impossible without a dummy "Adl" type to allow ADL to function. There are a lot of little places where the extent to which concepts work is entirely dependent on their order.
In simple cases, concepts allow you to do things similarly to Rust traits and get some of those powers, but in C++, a lot of little frustrations keep it from being as ergonomic or as powerful, and you still have to play stupid C++ name lookup games.
I remember a LOT of little annoyances, as well, in trying to partially constrain concepts, or express recursive concepts (which are very necessary for parsing a recursive format like CBOR). I tried a simple JSON parser that only allowed arrays and strings using C++ concepts and hit the same kinds of problems.
I'd recommend using concepts, I think, but keep in mind that trying to do anything at all generic or extensible might cause you to do more C++ detangling than you want.
Re:Defining interfaces in C++: concepts versus inheritance
by CyberRabbi on Thursday April 20, 2023 @05:36PM
(#35646698)
> Given an optimizing compiler, the first function (count(a)) is likely to just immediately return the size of the backing vector. The function is nearly free.
Re:Defining interfaces in C++: concepts versus inheritance
by munificent on Thursday April 20, 2023 @05:40PM
(#35646738)
That's true but devirtualization optimizations tend to be pretty brittle and it's very easy to fall of the optimizer's blessed path and end up back to doing to a virtual call without realizing it.
Worse, once the devirtualization optimization has failed, any further optimizations you would get from inlining the call will also fail.
If you're programming in C++, you probably do care about this level of performance, and in that case, it's nice to program in a style that guarantees it instead of hoping for a sufficiently smart compiler.
Re:Defining interfaces in C++: concepts versus inheritance
by CyberRabbi on Thursday April 20, 2023 @05:52PM
(#35646908)
> If you're programming in C++, you probably do care about this level of performance, and in that case, it's nice to program in a style that guarantees it instead of hoping for a sufficiently smart compiler.
Neither implementation guarantees any particular sequence of assembly instructions. Both require hoping that a sufficiently smart compiler will compile it to a sufficiently optimal sequence of instructions.
Re:Defining interfaces in C++: concepts versus inheritance
by CyberRabbi on Thursday April 20, 2023 @06:35PM
(#35647438)
> In practice, non-virtual function calls are reliably compiled to fairly efficient code while virtual calls are much less reliable.
Like I said, this echoes the conventional wisdom that most C++ developers seem to retain. The compiler landscape has changed since that wisdom was formed, since the advent of LTO and devirtualization optimizations.
Re:Defining interfaces in C++: concepts versus inheritance
by asdfws on Thursday April 20, 2023 @08:25PM
(#35648355)
So has programmer methodology. Throwing virtual calls everywhere is a relic of the past from the era of OOP fetishism. If you actually have statically verifiable leafs for virtual calls then you didn't even need a virtual call to begin with. It's code slop.
Re:Defining interfaces in C++: concepts versus inheritance
by i-use-nixos-btw on Thursday April 20, 2023 @08:25PM
(#35648356)
Strongly agree on the philosophy - but alas my experience with this specific case has not been great.
I’ve seen it simplify patches of code here and there, and that does apply to the trivial examples given in the post this topic links to - a function call involving a known child class. But add some basic real-world complexity and it quickly gets too complex for the optimiser to prove that it knows for sure what child class it is dealing with.
Re:Defining interfaces in C++: concepts versus inheritance
by kaba0 on Friday April 21, 2023 @06:56AM
(#35652024)
Unless you are in a hot loop (where you may not use virtual methods to begin with), I don’t think that performance difference is significant. Virtual calls have a slight overhead, but far from serious, and similarly not inlining something that you call only a single time for example is not the end of the world.
Re:Defining interfaces in C++: concepts versus inheritance
by munificent on Friday April 21, 2023 @01:12PM
(#35656901)
The problem with not inlining is less with the overhead of the function call itself, and more the loss of further optimization opportunities. Consider this (trivial) example:
main() {
int x = foo() + 3;
}
int foo() {
return 5;
}
Without inlining you have both the overhead of the call and the arithmetic addition. If you can inline the call then you get:
main() {
int x = 5 + 3;
}
But more importantly, the optimizer can now also eliminate the addition too:
main() {
int x = 8;
}
This is obviously a trivial example, but in real-world code, the optimization options opened up after inlining are important.
As far as I can see, neither gcc nor clang actually do this (unless the array has a fixed size; and even then in the gcc case only if the array is zero or one element long).
Probably still more efficient than a virtual call, but also a lot more complex on both the programmer and the compiler, and a lot harder to debug.
https://godbolt.org/z/7bTETsdd1
I tried using `final` to get them to figure out the inheritance case, but devirtualization didn't work when inlining a method defined on a base class. https://godbolt.org/z/h4GEYnzMo
Sure looks to me like GCC figures it out. Clang doesn't seem to, though.
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.
However, I have been writing Swift, every single day, since June, 2014, so I may have a bit more authority, there.
In this post, I describe a rather subtle bug that can happen, because of the differences between inheritance and interface: https://littlegreenviper.com/miscellany/swiftwater/the-curio...
It's actually something that happens to me fairly often, because I do mix them. I have learned to recognize it, when it happens. The best "cure," is to not use protocol defaults for properties and functions that I want to be implemented by inheritance hierarchies.
Thanks!
I think the purpose of concept is to add constraints to help the compiler to do static checking instead of just 'documenting your code'.
I'm slowly working my way through the C++ versions (I prefer to use things that are battletested) and mostly loving it except that compiler error messages are just getting more and more esoteric. Or rather, the error messages that have always been esoteric are getting more common since so much of modern C++ features rely on them.
I don't get your question. By definition, C++'s concepts are requirements specified on template arguments. Do you foresee any scenario where requirements on template arguments should not be used with templates?
I ended up abandoning it because recursive concepts weren't universally functioning (clang didn't support them right), and more nefariously, the C++ name definition rules made a whole lot of things very very painful. concepts were evaluated based on their definition site, not based on names available where they were expanded. This is true of templates to, but in concepts, it prevented me from allowing users to effectively extend my concepts-based library, and some circularly-dependent concepts were impossible without a dummy "Adl" type to allow ADL to function. There are a lot of little places where the extent to which concepts work is entirely dependent on their order.
In simple cases, concepts allow you to do things similarly to Rust traits and get some of those powers, but in C++, a lot of little frustrations keep it from being as ergonomic or as powerful, and you still have to play stupid C++ name lookup games.
I remember a LOT of little annoyances, as well, in trying to partially constrain concepts, or express recursive concepts (which are very necessary for parsing a recursive format like CBOR). I tried a simple JSON parser that only allowed arrays and strings using C++ concepts and hit the same kinds of problems.
I'd recommend using concepts, I think, but keep in mind that trying to do anything at all generic or extensible might cause you to do more C++ detangling than you want.
The compiler is able to do that with count_inheritance() as well if it's able to prove which instance of iter_base is used in the call. I suppose even many experienced C++ developers are not aware of this. This optimization is known as "devirtualization" and is fairly well-implemented in Clang and GCC. It's even more effective since the advent of LTO. Some more info: https://quuxplusone.github.io/blog/2021/02/15/devirtualizati... https://blog.llvm.org/2017/03/devirtualization-in-llvm-and-c...
Worse, once the devirtualization optimization has failed, any further optimizations you would get from inlining the call will also fail.
If you're programming in C++, you probably do care about this level of performance, and in that case, it's nice to program in a style that guarantees it instead of hoping for a sufficiently smart compiler.
Neither implementation guarantees any particular sequence of assembly instructions. Both require hoping that a sufficiently smart compiler will compile it to a sufficiently optimal sequence of instructions.
In practice, non-virtual function calls are reliably compiled to fairly efficient code while virtual calls are much less reliable.
Like I said, this echoes the conventional wisdom that most C++ developers seem to retain. The compiler landscape has changed since that wisdom was formed, since the advent of LTO and devirtualization optimizations.
I’ve seen it simplify patches of code here and there, and that does apply to the trivial examples given in the post this topic links to - a function call involving a known child class. But add some basic real-world complexity and it quickly gets too complex for the optimiser to prove that it knows for sure what child class it is dealing with.
Defining interfaces in C++ with ‘concepts’ (C++20) - https://news.ycombinator.com/item?id=35624899 - April 2023 (73 comments)