6 ms·
C++20 Concepts: The Definitive Guide
- eeegnu 5y agoSomething about type constraining auto just seems funny. I can see how it's useful from an error minimization / code clarity standpoint, but it almost seems counter to the point of even using auto.
- gmueckl 5y agoI belive that you do not sufficiently understand the giant footgun that is unconstrained auto, especially in the context of very template-heavy code. Concepts solve the issue that judicious use of auto will allow template instantiations to succeed that are plain wrong in that they will happily do the wrong thing just because the types involved fit the constraints by mere chance and not because they were meant to be used together like this.
- ncmncm 5y agoUnconstrained auto is unwise in globally accessible templates that participate in overload resolution, but is fine in lambda literals.
- gpderetta 5y agoInitially auto was not required (nor allowed). The concept name itself was enough. Adding auto is one of those compromises that please no one but was necessary to move the proposal forward
- s-luv-j 5y agoIs it just me, or have C++ errors gotten a lot better? Below example from guide seems a lot more ergonomic than in years past. test.cpp: In instantiation of ‘T add(T, T) [with T = std::basic_string<char>]’: test.cpp:17:21: required from here test.cpp:11:22: error: static assertion failed 11 | static_assert(std::is_integral_v<T>); | ~~~~~^~~~~~~~~~~~~~~~ test.cpp:11:22: note: ‘std::is_integral_v<std::basic_string<char> >’ evaluates to false Build finished with error(s).
- leni536 5y agoIt's not just you, dinostics continuously and noticably improve. A lot of effort is spent on this.
- tialaramex 5y agoThere has been more interest from modern programming languages and modern compiler developers in good diagnostics in two ways: 1. The earlier you report the problem, the cheaper the fix and 2. The better the diagnostic the more likely the programmer's fix is appropriate to the actual problem they had. I agree this is great news. I actually had to write MS SQL for the first time last week and it was disappointing but sadly expected to have it respond to a common but not technically-standard SQL syntax I'm used to with "Syntax error" as though somehow that's helpful. Such poor errors meant I spent more time reading the documentation than writing queries even though I've years of experience across several other SQL dialects.
- laszlokorte 5y agoOne of the major motivations for concepts is to allow better error messages.
- google234123 5y agoAnd ironically it’s questionable if it helps. Worse error messages were one of the main reasons it got blocked from 17.
- afranchuk 5y agoYeah, and besides the focus on error messages in past years, concepts will naturally allow template errors to be a _lot_ clearer, since they can serve as the specification of templated type requirements.
- Someone 5y agoOne of clang’s stated goals is “expressive diagnostics” See https://clang.llvm.org/diagnostics.html https://clang.llvm.org/diagnostics.html ; that page is not dated, but compares to gcc 4.9, which is from April 22, 2014. gcc also has worked on improving its error messages (most likely because of competition with clang), so that comparison probably isn’t accurate anymore.
- contravariant 5y agoI guess this is somewhat besides the point but checking if something can be multiplied by -1 is not great as a definition of subtraction. You'll typically want to use the additive inverse directly. I mean sure you can extend any commutative group into a module over the integers but you probably don't want to make this a hard requirement just to have subtraction.
- godelski 5y agoWhy would this be an issue? I'm under the impression that this is fine if we're operating with the standard addition operator and the standard field that everyone is used to working under. Isn't that the definition of the inverse? I understand that in different fields you have different operators but is that relevant here?
- contravariant 5y agoIf you were working with the standard field then why would you need to bother defining the general concept of subtraction? If you want to define the concept of subtraction then you probably don't want to assume you can multiply elements with an integer. Not that it can't be done but in general it will be a lot easier to define the additive inverse directly (if one exists).
- godelski 5y agoI mean but under the example that the author is doing they are working with standard integers (integrals). So this appears to me to still be the standard addition and subtraction. And with standard R1 subtraction it is the same as inverse of addition. I mean if we move into different coordinates and different fields, then yeah things change, but I don't see what the issue is with the example given here.
- MauranKilom 5y agoRandom example: In geometry code, you might distinguish between points and vectors, where point + vector = point, point - point = vector etc. It can then also be convenient to have a special type/value Origin, which (while functionally identical to point(0, 0)) allows you to e.g. write vector = point - origin to clearly express your intent of turning a vector into a point. -1 * origin is not meaningful, but point - origin is (while point + origin is not).
- google234123 5y agoAmazing that this took ~20 years to design and it still had to be rushed through to get into c++20…
- Koshkin 5y agoYou can take K&R C from my cold, dead hands. There was no header file hell. Often you didn't need a single header to be included in your code: most functions returned int (or nothing), and if you needed something that returned double, you could just say so. I remember being excited about function prototypes, but something was irretrievably lost at that point. The primal elegance of C as it was conceived by its creators is long forgotten now. (If you want, you can still experience it with the Tiny C Compiler that seems to continue to understand K&R C code just fine.)
- 37ef_ced3 5y agoI think you're trying to say you like C but dislike C++'s complexity. You will enjoy Go. Go can be understood as an improved, modernized C that doesn't abandon C's simplicity.
- the_duke 5y agoThe "Go is like C" comparison never made any sense to me. Go has a sophisticated runtime with transparent N:M threading and built-in concurrency primitives, Interfaces, garbage collection and a large standard library. Go is only simple when compared to the other languages that sit in a similar space, like Java and C#.
- pjmlp 5y agoC's runtime is UNIX, that is why we got POSIX.
- throw_m239339 5y ago> Go can be understood as an improved, modernized C that doesn't abandon C's simplicity. This is false, because Go has a garbage collector by default. This isn't an "improvement" in anyway but for those who don't care about memory management and predictable and deterministic performances.
- nrclark 5y agoAnother language-changing paradigm added to C++. I haven't even finished learning the old ones yet. Sometimes I think C++ would be better off if the committee stopped accepting proposals that add new features.
- AlexCoventry 5y agoScott Meyers thinks similarly, I think. https://www.youtube.com/watch?v=KAWA1DuvCnQ https://www.youtube.com/watch?v=KAWA1DuvCnQ
- ncmncm 5y agoScott Meyers was never a production coder. His schtick was teaching, and he got tired. The additions since he bowed out improve the experience of production coders.
- AlexCoventry 5y agoHe got tired because of the insane complexity of the language, and in the video I linked he supports his complaint with extensive and damning examples.
- ncmncm 5y agoHis difficulties are characteristic of someone not seeking solutions to actual daily engineering problems, and instead getting lost in a maze of language lawyering. If you approach features in terms of how they can be useful when coding, almost all of his difficulties never arise, or are easily sidestepped. For working coders that becomes second nature.
- heresie-dabord 5y ago> Another language-changing paradigm added to C++ I agree. Somewhere in the piled-high-and-deeper complexity of C++ there is one excellent, modern language that could be carved out. Maybe it is only the subset since C++17.
- 5y ago
- tialaramex 5y agoI feel like one of the things a "Definitive Guide" to this feature needs to make clear, and maybe even emphasise, is a key way these are different than say Rust type Traits. Concepts, just like the SFINAE and constrexpr hacks you should discard in their favour, are about only what will compile and you, the C++ programmer, are always responsible for shouldering the burden of deciding whether that will do what you meant even if you have no insight into the types involved. Example: In C++ the floating point type "float" matches a concept std::totally_ordered. You can, in fact, compile code that treats any container of floats as totally ordered. But of course if some of them are NaN that won't work, because NaNs in fact don't even compare equal to themselves. You, the C++ programmer were responsible for knowing not to use this with NaNs. Whereas, Rust's floating point type f32 implements PartialOrd (saying you can try to compare these) but not Ord (a claim that all of them are totally ordered). If you know you won't use NaNs you can construct a wrapper type, and insist that is Ord and Rust will let you do that, because now you pointed the gun at your foot, and it's clearly your fault if you pull the trigger. This is a quite deliberate choice, it's not as though C++ could have just dropped in Rust-style traits, but I think a "Definitive Guide" ought to spell this out so that programmers understand that the burden the concept seems to be taking on is in fact still resting firmly on their shoulders in C++. The other side of this is, if you wrote a C++ type say Beachball that implements the necessary comparison operators the Beachball is std::totally_ordered in C++ 20 with no further work from you to clear up this fact. Your users might hope you'll document whether Beachballs are actually totally ordered or not though... I think this will likely prove to be a curse, obviously its proponents think it will work out OK or even a blessing.
- svalorzen 5y agoAre you sure about this? In my tests floating points are always considered partially ordered, not totally ordered. This page [0] even mentions this in the notes towards the bottom. [0]: https://en.cppreference.com/w/cpp/utility/compare/partial_ordering https://en.cppreference.com/w/cpp/utility/compare/partial_or...
- tialaramex 5y agoNote that std::totally_ordered is a concept (this topic is about "C++ 20 Concepts: The Definitive Guide") whereas you're talking about std::partial_ordering which is a class, also introduced in C++ 20. Specifically these ordering classes are the result of the spaceship operator and the concept doesn't care whether you have a spaceship operator.
- pharmakom 5y agoIs this a scrappy version of type classes (Haskell) or traits (Rust)?
- steveklabnik 5y agoSort of, in that they are a thing you do to constrain generic parameters. A significant difference in my understanding is that type classes and traits are required, but concepts are not. That is, using the example from the article, a concept can tell you if you're passing something that doesn't add, but you can add inside a function without using a concept. In other words: fn add<T>(x: T, y: T) -> T { x + y } This won't compile in Rust: error[E0369]: cannot add `T` to `T` --> src/lib.rs:2:7 | 2 | x + y | - ^ - T | | | T | help: consider restricting type parameter `T` | 1 | fn add<T: std::ops::Add<Output = T>>(x: T, y: T) -> T { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ This suggestion works, but you don't have to write it this way. Once the constraints get more complex than T: Foo, I personally switch to this form: use std::ops::Add; fn add<T>(x: T, y: T) -> T where T: Add<Output = T>, { x + y } I find it a little easier to read. YMMV. Whereas in C++, this does compile: template<typename T> T add(T a, T b) { return a + b; } If you try to add something that doesn't have + defined: int main(void) { add("4", "5"); } you get this <source>:4:12: error: invalid operands to binary expression ('const char *' and 'const char *') return a + b; ~ ^ ~ <source>:8:5: note: in instantiation of function template specialization 'add<const char *>' requested here add("4", "5"); ^ Whereas, if you do what the article does (though I'm using char* instead of std::string, whatever) #include <concepts> template<typename _Tp> concept integral = std::is_integral_v<_Tp>; template<std::integral T> T add(T a, T b) { return a + b; } int main(void) { add("4", "5"); } you get <source>:12:5: error: no matching function for call to 'add' add("4", "5"); ^~~ <source>:6:3: note: candidate template ignored: constraints not satisfied [with T = const char *] T add(T a, T b) ^ <source>:5:15: note: because 'const char *' does not satisfy 'integral' template<std::integral T> ^ /opt/compiler-explorer/gcc-11.1.0/lib/gcc/x86_64-linux-gnu/11.1.0/../../../../include/c++/11.1.0/concepts:102:24: note: because 'is_integral_v<const char *>' evaluated to false concept integral = is_integral_v<_Tp>; ^ This doesn't feel like a huge change because add is such a small function, but if it were larger and more complicated, the error with a concept is significantly better.
- Razengan 5y agoI’m sorry for this petty criticism, but every time I gaze upon “modern” C++ I wonder if the language could get any uglier, and with every revision it somehow manages to.
- ncmncm 5y agoThe most important thing to say about this "definitive guide" is that it delays to the end presenting the overwhelmingly most important detail about Concepts: how to use them. The right place to put a concept name, in production code, is in place of "typename" in a template definition, or even better in place of "T" in the function argument declaration. That is, instead of template<typename T> T add(T a, T b) requires addable<T> ( return a + b; ) say template <addable T> T add(T a, T b) { return a + b; } or auto add( addable auto a, addable auto b) { return a + b; } according as whether you want to enforce a and b to have the same type (which is another omission). Most often it is not necessary, and not wanted, to enforce a and b having the same type.
- deleted 5y ago[deleted]
- CodeMage 5y ago> Most often it is not necessary, and not wanted, to enforce a and b having the same type. That really depends on what you're trying to do. Presenting these two different declarations as somehow equivalent is very misleading and I'm glad that the author didn't do that.
- ncmncm 5y agoThey are not presented as equivalent, but as a choice. The author's failing was in presenting neither of them until long after they were due, and presenting thoroughly inferior alternatives in the meantime.
- creativeCak3 5y agoAm I going insane or does the following does NOT work(?): <code> template<typename T> T mul(T a, T b) { return ab; } template<typename T> T mul(T a, int b) { std::string ret_val{std::move(a)}; a.reserve(a.length() b); auto start_pos{a.begin()}; auto end_pos{a.end()}; for(int i = 0; i < b; i++) { std::copy(start_pos, end_pos, std::back_inserter(a)); } return ret_val; } </code> I knew it looked odd to me for some reason...I had to re-write it as follows: <code> template<typename T> T mul(T a, T b) { return ab; } template<typename T> T mul(T a, int b) { std::string ret_val{}; ret_val.reserve(a.length() b); auto start_pos{a.begin()}; auto end_pos{a.end()}; for(int i = 0; i < b; i++) { std::copy(start_pos, end_pos, std::back_inserter(ret_val)); } return ret_val; } </code> Did I miss something??
- vips7L 5y agoFour spaces before each line will format your code so other people can read it.
- steveklabnik 5y agoYou only need two on Hacker News, not four. https://news.ycombinator.com/formatdoc https://news.ycombinator.com/formatdoc
- vips7L 5y agoTIL thanks.
- account42 5y ago> The above definition of add is equivalent to the one using static_assert. They are not though - the equivalent C++17 would be template<typename T> std::enable_if_t<std::is_intregral_v<T>, T> add(T a, T b) { return a + b; } The difference is that these make the function unavailable for overload resolution for non-integral T but another implementation might still cover them while the static_assert version leaves the function available for overload resolution but then errors if it is selected.
- fithisux 5y agoIs this in Dlang?