Live data from Hacker News

Defining interfaces in C++ with ‘concepts’ (C++20)

lemire.me

71–77 of 77 posts

Re: Defining interfaces in C++ with ‘concepts’ (C++20)

#71
post #69
post #68

Earlier quoted context omitted.

A nominal type system is not a more constrained version of a structural one. That statement would imply that any program written for the former would work using the latter as well, which is false. Name collisions would simply not resolve. For it to work, you need to add a namespace to all the colliding methods (a simple one would be a prefix like people do in C). A nominal system is a more constrained structural syst…

Hmmh. You seem to be restating what was said above. A nominal type system still is superseded by a structural type system. The difference is in how a type is defined. Or what kind of constraints are in entailment said otherwise. An interface enforces constraints. The difference here is merely that the current implementations only have either one of these type of interfaces. So for the structural type system, all meth…

Yeah I think our arguments overlap in some ways.

> That's all. Because our current languages are this way doesn't mean that the two concepts cannot be reconciliated or that one is just better than the other.

I don't think I agree with this though, I believe they're fundamentally different. The whole point of structural constraints is that they don't need the type to be aware of them. The point of nominal constraints though is that they require the type to explicitly acknowledge them.

In an ideal situation, everyone names and types things the same ('logical') way, so structural constraints 'just work'. A type implements has_organ, and an interface requires has_organ, and the type is automatically compatible with the interface.

A nominal system is the opposite though; the type explicitly understands what a specific interface implies and formally states it.

I just can't see how there's a subset-superset relationship, or how they can somehow be reconciled.

Re: Defining interfaces in C++ with ‘concepts’ (C++20)

#72
post #71
post #69

Earlier quoted context omitted.

Hmmh. You seem to be restating what was said above. A nominal type system still is superseded by a structural type system. The difference is in how a type is defined. Or what kind of constraints are in entailment said otherwise. An interface enforces constraints. The difference here is merely that the current implementations only have either one of these type of interfaces. So for the structural type system, all meth…

Yeah I think our arguments overlap in some ways. > That's all. Because our current languages are this way doesn't mean that the two concepts cannot be reconciliated or that one is just better than the other. I don't think I agree with this though, I believe they're fundamentally different. The whole point of structural constraints is that they don't need the type to be aware of them. The point of nominal constraints…

One way to see it is that a type has a given methods located in a given namespace in the nominal type system.

A nominal type system doesn't necessarily enforce semantics either.

It just enforces the location of a method definition.

Seen that way, because the relation is dual, one could indeed claim that a structural interface is a nominal interface where the name constraint is elided.

But just as in subtyping, one less constraint also means bigger set.

Of course if one were to decide that an object satisfying a nominal interface doesn't satisfy the structural interface obtained by ignoring the namespace, then I'd agree as well, these concepts would be disjoint.

I don't think they are though but I don't know of a language that ever mixed both either.

Re: Defining interfaces in C++ with ‘concepts’ (C++20)

#73
post #35

does this imply that type erasure via base classes will become a thing of past ?

concepts don't change codegen in any way, they just mean your error messages become actually sane :D

Essentially, in the past you would have your template code, say something dumb like:

    template  T halve(T t) { return t / 2; }
and if you instantiated with the wrong type, say:

    halve("foo")
you get an error message pointing to the t/2 in the halve implementation. As your templates become less trivial, so do the error messages. So we could apply concepts:

    template  concept Halvable = requires (T t) { t / 2; };
    template  T halve(T t) { return t / 2; }
Now our call to halve("foo") will complain that const char* is not Halvable. It will also produce an error similar to the original saying that we don't conform to Halvable because the `t / 2` expression fails. In this case it's not super valuable, but if you were instantiating a type or method that was more complicated instead of getting dozens or hundreds of errors in the instantiated body of the template, you just get an early error saying "you aren't conforming to X for these reasons:...".

Unfortunately this does not stop you writing a template that depends on things that your concepts don't guarantee. For example, if we can halve something, we must be able to double it, right? (silly example to demonstrate the issue)

    template  T double_it(T t) { return t * 2; }
Note, Halvable doesn't ensure that * is available, but this template is still "correct".

Now we can do `double_it(1)` and that will work, but `double_it("foo")` will say the error is at the t * 2 in the body, when we probably want the error to actually at the point we try to call double_it.

Preventing this kind of error is non-trivial (possibly actually impossible?) given how concepts are defined. It's literally just a list of statements and expressions that need to be valid for a type. But going from a list of "these statements and expressions are valid" to "is this specific expression or statement valid in a template" is at best nontrivial. This is core limitation of the entire feature.

Re: Defining interfaces in C++ with ‘concepts’ (C++20)

#74

Earlier quoted context omitted.

I have been programming in C++ for almost 20 years [1] and I don't remember ever being bitten by accidental concept conformance. So I object to the "likely very common" description. Implicit conformance was very much an explicit design goal. [1] yes, concepts as an explicit language feature are new, but C++ has had de-facto concepts since Stepanov work on the original STL in the 90s.

Since I know better than to suggests C++ programmers might be more capable of making mistakes than they realise, lets try a different question: How do you spot this mistake when reviewing other people's code? Do you memorise a list of all the semantic requirements of each concept so that you can mentally check that the concept's requirements are satisfied appropriately by what was written each time ?

This isn't something I look for in code reviews because it's just not something I've ever see be the source of a bug. There are a million bugs that I've eventually tracked down to some subtle C++ thing, but I've never had one come down to a type which appears to conform to one of the standard library's concepts but actually doesn't.

Re: Defining interfaces in C++ with ‘concepts’ (C++20)

#75
post #72
post #71

Earlier quoted context omitted.

Yeah I think our arguments overlap in some ways. > That's all. Because our current languages are this way doesn't mean that the two concepts cannot be reconciliated or that one is just better than the other. I don't think I agree with this though, I believe they're fundamentally different. The whole point of structural constraints is that they don't need the type to be aware of them. The point of nominal constraints…

One way to see it is that a type has a given methods located in a given namespace in the nominal type system. A nominal type system doesn't necessarily enforce semantics either. It just enforces the location of a method definition. Seen that way, because the relation is dual, one could indeed claim that a structural interface is a nominal interface where the name constraint is elided. But just as in subtyping, one le…

AFAIK Python [optional] type system supports both. The nominal types are the "common" types, while the protocols [1] are structural. It's quite cool, actually :)

[1] https://peps.python.org/pep-0544/

Re: Defining interfaces in C++ with ‘concepts’ (C++20)

#76

Earlier quoted context omitted.

C++ templates have never used runtime dispatch

I assume you've checked the version control history of every C++ compiler in existence?

I have used C++ before templates were a thing, and never ever saw one that did otherwise, added C++ to my toolbox in 1993.

Re: Defining interfaces in C++ with ‘concepts’ (C++20)

#77
post #73
post #35

does this imply that type erasure via base classes will become a thing of past ?

concepts don't change codegen in any way, they just mean your error messages become actually sane :D Essentially, in the past you would have your template code, say something dumb like: template T halve(T t) { return t / 2; } and if you instantiated with the wrong type, say: halve("foo") you get an error message pointing to the t/2 in the halve implementation. As your templates become less trivial, so do the error me…

Arguably, pointing to t/2 (in the second case) is the correct behaviour, because (if concepts are being used), then it is the function (double_it) that is mis-specifying it’s requirements. Even better would be to point at the function definition as well and say that there is nothing in the concept that allows this function.

I think the core problem (don’t know if this is still the case, haven’t actually used concepts) is that templates raise errors at the point of expansion, rather than at the point of definition, because of how they are specified/implemented.

I think it’s possible that the compiler could do something smart by auto-creating a temp type that is the minimal possible implementation of the concept and attempting to compile the function. Any errors that result, should be flagged at the concept specified in the function.

Post reply on HN