Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

>That said, I've mostly reached the conclusion that much of this is unavoidable. Systems languages need to have lots of detail you just don't need in higher level languages like Haskell or Python, and trait impls on arbitrary types after the fact is very powerful and not something I would want to give up.

Have you checked out C++20 concepts? It supports aliases and doesn't require explicit trait instantiations, making it possible to right such generic code with much less boilerplate.



My experience in C++ prior to 20 is that it is a lot more verbose/boilerplatey than Rust. I'd love to see that get better, but I think C++ is starting from significantly behind.


C++17:

    template<typename T, std::enable_if_t<std::is_floating_point_v<T>, int>* = nullptr>
    void func(T fp) { ... }
C++20:

    void func(std::floating_point auto fp) { ... }


There is an equivalent syntax in Rust to both of those examples, and in both cases I find it less verbose. The template variant is roughly equivalent to:

    fn func<T: FloatingPoint>(fp: T) { ... }
And the "auto" variant is similar to impl argument in Rust:

    fn func(fp: impl FloatingPoint) { ... }


I really don't see in which way the second case is less verbose especially if you add a non-void return type, e.g. i32. The first case would also be doable like this, which is pretty munch the exact same than your first example with the added "template" keyword

    template<std::floating_point T> 
    void func(T t) { ... }
(also, it's not really equivalent - if I'm not mistaken with traits you can only use what the trait declares ; in C++ you can for instance do something like

    template<typename T>
    concept CanBlah = requires (T t) { 
      t.blah();
    };
and still be able to do

    void func(CanBlah auto t) { 
      log << "func:" << t;
      t.blah();
    }
instead of polluting the prototype with all possible side concerns)


> instead of polluting the prototype with all possible side concerns)

C++ Concepts are duck typed, and Rust's Traits are not, so in Rust you are expressing meaning here, and in C++ only making some claims about syntax which perhaps hint at meaning.

WG21 seems to dearly wish this wasn't so, offering Concepts which pretend to semantics they don't have, such as std::totally_ordered and I'm glad to see your "CanBlah" concept doesn't do this, to be sure all things which match this requirement can, indeed blah() although we've no idea what that can or should do.

Once you've accepted that you only have duck typing anyway, you're probably going to have to explain in your documentation the actual requirements for this parameter t, as the prototype merely says it CanBlah and that's not actually what we care about.

In contrast the Rust function we looked at actually does tell us what is required here, something which "implements FloatingPoint", and that implementation (plus the data structure itself) is all that's being exposed.


I don't understand - you seem to say that duck typing is a bad thing. In my experience, some parts of a program have to be strongly typed and some have to be "lightly" - I'd say that a good 5% of my work is to make C++ APIs that look&feel closer to dynamic languages with even less typing checks than the C++ baseline.

> WG21 seems to dearly wish this wasn't so

how so ?

> Once you've accepted that you only have duck typing anyway, you're probably going to have to explain in your documentation the actual requirements for this parameter t, as the prototype merely says it CanBlah and that's not actually what we care about.

the documentation having to state "t can be logged" would just be useless noise and a definite no-pass in code review aha

> In contrast the Rust function we looked at actually does tell us what is required here, something which "implements FloatingPoint", and that implementation (plus the data structure itself) is all that's being exposed.

my personal experience from other languages with similar subtyping implementation (ML-ish things) is that this looks good in theory but is just an improductive drag in practice


> you seem to say that duck typing is a bad thing

C++ didn't have any other practical choice here.

But this introduced another case where C++ has a "false positive for the question: is this a program?" as somebody (Chandler Carruth maybe?) has put it. If something satisfies a Concept, but does not model the Concept then the C++ program is not well formed and no diagnostic is required.

> how so ?

I provided an explanation with an example, and you elided both.

> the documentation having to state "t can be logged" would just be useless noise and a definite no-pass in code review aha

In which case it's your responsibility to ensure you can log this, which of course CanBlah didn't express.

> similar subtyping implementation

The only place Rust has subtyping is lifetimes, so that &'static Foo can substitute for any &'a Foo and I don't think that's what you're getting at.


> WG21 seems to dearly wish this wasn't so, offering Concepts which pretend to semantics they don't have, such as std::totally_ordered and I'm glad to see your "CanBlah" concept doesn't do this, to be sure all things which match this requirement can, indeed blah() although we've no idea what that can or should do.

I don't understand how random assumptions on what WG21 may or may not think counts as an example (or anything to be honest)

> If something satisfies a Concept, but does not model the Concept then the C++ program is not well formed and no diagnostic is required.

uh... no ?

I think that you are referring to this blog post: https://akrzemi1.wordpress.com/2020/10/26/semantic-requireme... for which I entirely disagree with the whole premise - the only, only thing that matters is what the compiler understands. The standard can reserve itself the right to make some cases UB, such as when trying to sort un-sortable things just like it can state that adding to numbers can cause UB and that's fine: it's the language's prerogative and is all to be treated as unfortunate special-cases ; for the 99.99999% remaining user code, only the code matters and it makes no sense to ascribe a deeper semantic meaning to what the code does.

> In which case it's your responsibility to ensure you can log this, which of course CanBlah didn't express.

A metric ton of side concerns should not be part of the spec, such as logging, exceptions, etc - everyone saw how terrible and counter-productive checked exceptions were in java for instance. Specifying logging explicitly here would be a -2 in code review as it's purely noise: the default assumption should be that everything can log.

> The only place Rust has subtyping is lifetimes, so that &'static Foo can substitute for any &'a Foo and I don't think that's what you're getting at.

I meant polymorphism, my bad


> uh... no ?

Actually, yes. I was hoping people like you were familiar, but that was actually more to ask of you than I'd assumed since C++ has a tremendous amount of such language in the standard, going in I'd figured hey maybe there's a half dozen of these and I was off by at least one order of magnitude. That's... unfortunate.

Exactly which language ended up being in the "official" ISO standard I don't know, but variations on this are in various circulating drafts through 2020 "[if] the concept is satisfied but not modeled, the program is ill-formed, no diagnostic required", if you're trying to find it in a draft you have, this is in the Libraries section in drafts I looked at, although exactly where varies. [ Yes that means in principle if you completely avoid the C++ standard library this doesn't apply to you... ]

> I think that you are referring to this blog post

It's possible that I've read Andrzej's post (I read a lot of things) but I was just reporting what the standard says and all Andrzej seems to be doing there is stating the obvious. Lots of people have come to the same conclusion because it isn't rocket science.

> only the code matters and it makes no sense to ascribe a deeper semantic meaning to what the code does.

This might be a reasonable stance if the code wasn't written by people. But it is, and so the code is (or should be) an attempt to express their intent which is in fact semantics and not syntax.

But let's come back to std::totally_ordered, although you insist it doesn't "count as an example" for some reason, it is in fact a great example. Here's a standard library concept, it's named totally_ordered, so we're asking for a totally ordered type right? Well, yes and no. Semantically this is indeed what you meant, but C++ doesn't provide the semantics, C++ just gives you the syntax check of std::equality_comparable, and if that's a problem you're referred to "No diagnostic required".


> "[if] the concept is satisfied but not modeled, the program is ill-formed, no diagnostic required",

Couldn't find anything ressembling this in the section of the standard describing concepts and constraints. The spec is very clear (C++20 7.5.7.6):

> The substitution of template arguments into a requires-expression may result in the formation of invalid types or expressions in its requirements or the violation of the semantic constraints of those requirements. In such cases, the requires-expression evaluates to false; it does not cause the program to be ill-formed.

Maybe the stdlib has different ording, but the stdlib can literally have any wording it wants and could define std::integer to yield 2+2 = 5 without this being an issue.

> [ Yes that means in principle if you completely avoid the C++ standard library this doesn't apply to you... ]

in just a small library i'm writing, there's already ten-fold the number of concepts than there are defined in the standard library, so I'd say that this does not apply in general ; the stdlib is always an irrelevant special case and not representative of the general case of the language, no matter how hard some wish it. E.g. grepping for 'concept [identifier] =' in my ~ yields 2500 results, with only a small minority of those being the std:: ones.

> This might be a reasonable stance if the code wasn't written by people. But it is, and so the code is (or should be) an attempt to express their intent which is in fact semantics and not syntax.

I think this is very misguided. I am not programming for humans to process my code, but for computers to execute it. That's what comes first.

> Semantically this is indeed what you meant,

no, if I type std::totally_ordered, I mean "whatever the language is supposed to do for std::totally_ordered", and exactly nothing else


> no, if I type std::totally_ordered, I mean "whatever the language is supposed to do for std::totally_ordered", and exactly nothing else

That's easy then, if you mean "whatever the language is supposed to do for std::totally_ordered" you could say what that is exactly, right?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: