Live data from Hacker News

C++20 Concepts: The Definitive Guide

thecodepad.com

31–40 of 102 posts

Re: C++20 Concepts: The Definitive Guide

#32

Is this a scrappy version of type classes (Haskell) or traits (Rust)?

Sort 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(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>(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(x: T, y: T) -> T
    where
        T: Add,
    {
        x + y
    }
    
I find it a little easier to read. YMMV.

Whereas in C++, this does compile:

    template
    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

  :4:12: error: invalid operands to binary expression ('const char *' and 'const char *')
    return a + b;
           ~ ^ ~
  :8:5: note: in instantiation of function template specialization 'add' requested here
      add("4", "5");
      ^
Whereas, if you do what the article does (though I'm using char* instead of std::string, whatever)

    #include 
    template
    concept integral = std::is_integral_v;
    
    template
    T add(T a, T b)
    {
      return a + b;
    }
    
    int main(void) {
        add("4", "5");
    }
you get

    :12:5: error: no matching function for call to 'add'
        add("4", "5");
        ^~~
    :6:3: note: candidate template ignored: constraints not satisfied [with T = const char *]
    T add(T a, T b)
      ^
    :5:15: note: because 'const char *' does not satisfy 'integral'
    template
                  ^
    /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' evaluated to false
        concept integral = is_integral_v;
                           ^
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.

Re: C++20 Concepts: The Definitive Guide

#33

I 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 mean…

Are 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_or...

Note 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.

Re: C++20 Concepts: The Definitive Guide

#34

I 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 mean…

How can floats be totally ordered. This isn’t even a matter of NaNs or not. A set of floats where two or more floats compare equal does not permit a total ordering.

Re: C++20 Concepts: The Definitive Guide

#35

I 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 mean…

How can floats be totally ordered. This isn’t even a matter of NaNs or not. A set of floats where two or more floats compare equal does not permit a total ordering.

What do you mean? I'm pretty sure no two distinct floats compare as equal.

Re: C++20 Concepts: The Definitive Guide

#36
post #11

Another 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.

C++23 vs C++20 seem to be less drastic. So maybe they will slow down to a slow trickle at some point ;)

Re: C++20 Concepts: The Definitive Guide

#37
post #11

Another 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.

I think there's more to it (changing the language) than you are crediting it.

First, you don't have to use the new features (though eventually you'll be reading the code of people who did, so this is only half-valuable). There is new c++11 code being written every day -- in volume (a hard to pin down amount) it's sadly more than 50%. The usage surveys don't really capture this clearly (and it's not clear they could).

Second: often new features are for library writers, or are out there for library writers to use (e.g. coroutines, which probably will not be appropriate for many users before c++23, but pretty much need to be available for people to experiment with).

Third: the new features tend to be additive. For example you don't need to use many of the stuff in -- stick to a for loop unless you want to take advantage of some new capability (e.g. policies, which are't ubiquitous). Concepts are the same way: they will improve error messages and reduce bugs, but if you don't use them your code will in 99.9% work just fine. When you see a very simple example that uses concepts, it's not surprising that the concepts don't really improve a simple add function -- the case is deliberately simple for explanatory purposes.

Languages move forward. Even go recently caved and added in generics.

Re: C++20 Concepts: The Definitive Guide

#38

Earlier quoted context omitted.

How can floats be totally ordered. This isn’t even a matter of NaNs or not. A set of floats where two or more floats compare equal does not permit a total ordering.

What do you mean? I'm pretty sure no two distinct floats compare as equal.

-0.0 and +0.0?

Re: C++20 Concepts: The Definitive Guide

#39

Earlier quoted context omitted.

How can floats be totally ordered. This isn’t even a matter of NaNs or not. A set of floats where two or more floats compare equal does not permit a total ordering.

What do you mean? I'm pretty sure no two distinct floats compare as equal.

-0 and 0 do.

Re: C++20 Concepts: The Definitive Guide

#40
The 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
  T add(T a, T b)
      requires addable (
    return a + b;
  )
say

  template 
  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.

Post reply on HN