5 ms·
Defining interfaces in C++: concepts versus inheritance
- deleted 4y ago[deleted]
- bramblerose 4y ago"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 4y agoIts 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 4y agoIf 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 https://godbolt.org/z/7bTETsdd1
- celrod 4y agoClang 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 https://godbolt.org/z/h4GEYnzMo
- nemetroid 4y agoYou'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.
- kllrnohj 4y agohttps://godbolt.org/z/4WxGj7c65 https://godbolt.org/z/4WxGj7c65 Sure looks to me like GCC figures it out. Clang doesn't seem to, though.
- butterisgood 4y agoI wonder if the author intended to use private inheritance?
- deweywsu 4y ago[dead]
- nemetroid 4y agoStructs 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).
- meragrin_ 4y agoNo. 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.
- ChrisMarshallNY 4y agoI 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-curious-case-of-the-protocol-default/ 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 4y agoI think you're referring to the SR-103 issue: https://github.com/apple/swift/issues/42725 https://github.com/apple/swift/issues/42725
- ChrisMarshallNY 4y agoThat looks like it! Thanks!
- synergy20 4y ago"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'.
- klik99 4y agoConcepts 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.
- Taywee 4y agoOne 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.
- klik99 4y agoThanks! 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.
- bobbyi 4y agoConcepts 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.
- simplotek 4y ago> 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?
- Taywee 4y agoI'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 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.
- dang 4y agoRecent and related: Defining interfaces in C++ with ‘concepts’ (C++20) - https://news.ycombinator.com/item?id=35624899 https://news.ycombinator.com/item?id=35624899 - April 2023 (73 comments)
- CyberRabbi 4y ago> 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/devirtualization/ https://quuxplusone.github.io/blog/2021/02/15/devirtualizati... https://blog.llvm.org/2017/03/devirtualization-in-llvm-and-clang.html https://blog.llvm.org/2017/03/devirtualization-in-llvm-and-c...
- munificent 4y agoThat'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 4y ago> 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 4y agoYes, 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.