[HN Gopher] Defining interfaces in C++: concepts versus inheritance
___________________________________________________________________
Defining interfaces in C++: concepts versus inheritance
Author : jandeboevrie
Score : 53 points
Date : 2023-04-20 19:03 UTC (3 hours ago)
(HTM) web link (lemire.me)
(TXT) w3m dump (lemire.me)
| synergy20 wrote:
| "So what are concepts good for? I think it is mostly about
| documenting your code".
|
| I think the purpose of concept is to add constraints to help the
| compiler to do static checking instead of just 'documenting your
| code'.
| ChrisMarshallNY wrote:
| I haven't written C++ in many years, so I don't feel qualified to
| comment on C++.
|
| 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.
| meindnoch wrote:
| I think you're referring to the SR-103 issue:
| https://github.com/apple/swift/issues/42725
| ChrisMarshallNY wrote:
| That looks like it!
|
| Thanks!
| dang wrote:
| Recent and related:
|
| _Defining interfaces in C++ with 'concepts' (C++20)_ -
| https://news.ycombinator.com/item?id=35624899 - April 2023 (73
| comments)
| [deleted]
| butterisgood wrote:
| I wonder if the author intended to use private inheritance?
| nemetroid wrote:
| 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).
| deweywsu wrote:
| [dead]
| bramblerose wrote:
| "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.
| crickey wrote:
| 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.
| nemetroid wrote:
| _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.
|
| https://godbolt.org/z/7bTETsdd1
| celrod wrote:
| 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
| kllrnohj wrote:
| https://godbolt.org/z/4WxGj7c65
|
| Sure looks to me like GCC figures it out. Clang doesn't seem
| to, though.
| klik99 wrote:
| 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.
| bobbyi wrote:
| 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.
| Taywee wrote:
| 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.
| Taywee wrote:
| 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 too, 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.
| CyberRabbi wrote:
| > 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.
|
| 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...
| munificent wrote:
| 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.
| CyberRabbi wrote:
| > 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.
| munificent wrote:
| Yes, in principle a compiler is free to generate
| arbitrarily horrendous code regardless of what you ask it
| to do.
|
| In practice, non-virtual function calls are reliably
| compiled to fairly efficient code while virtual calls are
| much less reliable.
| CyberRabbi wrote:
| > 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.
___________________________________________________________________
(page generated 2023-04-20 23:02 UTC)