[HN Gopher] Defining interfaces in C++ with 'concepts' (C++20)
___________________________________________________________________
Defining interfaces in C++ with 'concepts' (C++20)
Author : pjmlp
Score : 60 points
Date : 2023-04-19 06:26 UTC (16 hours ago)
(HTM) web link (lemire.me)
(TXT) w3m dump (lemire.me)
| formerly_proven wrote:
| I would argue Java interfaces are very different from Go
| interfaces and C++ concepts, because the former is nominally
| typed and the latter are structural.
| aatd86 wrote:
| That's something I have been pondering for some time.
|
| I believe it's a false dichotomy.
|
| My thought is still that _structural_ supersedes _nominal_.
|
| A nominal interface is just another constraint added to the
| list of constraints of an underlying structural interface?
| mjburgess wrote:
| Well it depends on a runtime/compile-time distinction. A
| nominal type is a structural type with a compile-time
| constraint.
|
| If you have compile-time only constants you can model
| nominals with structural, type Square
| static const IsSquare = true var
| length = 10
|
| You can kinda hack-in subtyping, type Shape
| static const Shape = true type Square
| import static from Shape static const IsSquare =
| true var length = 10
| gpderetta wrote:
| This is routinely done in c++ with tags (for example
| iterator_tag). Tags inheritance is also a thing.
| stefncb wrote:
| In a nominal type system, a method x() is part of the
| interface X, while in a structural one it's part of the
| implementor of said interface. In Go there's a
| Human.HasOrgan(), not an AbstractBody.HasOrgan().
|
| A consequence of this is that in Rust, which has a nominal
| system, you can implement two traits that contain a method
| with the same name and are required to disambiguate at the
| call site. In Go you can't do that, since the method is part
| of the concrete type.
| aatd86 wrote:
| Fair. That's not really in contradiction too.
|
| The additional naming constraint added to a structural
| interface would form a sort of namespace for methods.
|
| I think in the comments below that someone likens this to
| tags in C++.
| stefncb wrote:
| 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 system
| in some ways, but the opposite is true as well, so it's
| not as simple as 'nominal is subset of structural'.
| aatd86 wrote:
| 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 methods are in the global namespace,
| somehow.
|
| 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.
| stefncb wrote:
| 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.
| aatd86 wrote:
| 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.
| jcheng wrote:
| Nominal interfaces can still be useful though, as they convey
| a stronger sense of intent than structural. For example,
| java.io.Serializable is a completely empty interface that
| classes "implement" to signal that they are safe to
| serialize. As a structural interface, it'd be useless.
| gpderetta wrote:
| structurally you can do something similar by adding some
| tag (in the form of a constant or nested type) to your
| class. For example: template<class T>
| concept serializable = requires { typename
| T::is_serializable_tag; }; void
| serialize(serializable auto x) {...}; struct
| NotSerializableClass { ... }; struct
| SerializableClass { using is_serializable_tag = void; ...
| }; serialize(NotSerializableClass{}); // error
| serialize(SerializableClass{}); // all good
|
| In C++, specializing a trait is also an option. So, while
| nominal and structural interfaces are not the same,
| sometimes the lines are blurred.
| tialaramex wrote:
| In practice C++ Concepts don't do what you're suggesting.
|
| The C++ 20 Standard Library provides numerous concepts which
| have a very different _semantic_ requirement than the syntax
| they 're checking. If you violate the syntactic requirement
| of course that'll earn you a compiler error, _but_ if you
| violate the semantic requirements that 's silently an ill-
| formed C++ program, it has no meaning whatsoever and might do
| absolutely anything if run.
|
| If these were nominal, we could say, well, nobody should have
| deliberately implemented this inappropriate Concept, similar
| to an _unsafe_ Rust trait, the act of implementation is a
| promise to others. But C++ Concepts aren 't nominal and so
| there was no opportunity to do that and so in practice such
| deviations are likely very common despite the potentially
| drastic consequences.
| gpderetta wrote:
| 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.
| tialaramex wrote:
| 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 ?
| gpderetta wrote:
| I expect other people to write tests (including compile
| time tests).
| tialaramex wrote:
| If you were worried about behavioural problems, including
| UB, tests would help.
|
| But alas the problem here is IFNDR [Ill-formed No
| Diagnostic Required] so the compiler can't help you. All
| semantic constraints are your problem as the programmer,
| C++ decided that it's not the compiler's concern whether
| the program meets semantic constraints. Testing doesn't
| necessarily help at all, which is probably surprising.
| hoseja wrote:
| uint32_t next() { index++; return array[index - 1]; }
|
| I do like uint32_t next() { return
| array[index++]; }
|
| , shame it's kinda unintuitive.
| robocat wrote:
| Really good video by Conor Hoekstra on the differences between
| C++ Concepts vs. Haskell Typeclasses vs. Rust Traits vs. Swift
| Protocols.
|
| https://youtu.be/E-2y1qHQvTg
|
| It is a good intro overview to the subject. Unfortunately the
| video has far too much filler, so you need to skip a bit e.g.
| start at 22:30 when the video gets into examples.
| signa11 wrote:
| does this imply that type erasure via base classes will become a
| thing of past ?
| gpderetta wrote:
| In C++20, concepts don't really provide any new semantic
| feature that wasn't already available before. Just a nicer and
| cleaner syntax (often considerably so).
| signa11 wrote:
| i see, ok thanks ! so, the concept-model idiom is still
| (pretty) useful...
| olliej wrote:
| 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 <typename T> 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 <typename T> concept Halvable = requires (T t) { t /
| 2; }; template <Halvable T> 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 <Halvable T> 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.
| unwind wrote:
| As always, very informative and a perfect "snippet" size to
| quickly read and learn a new thing or two. Thanks!
|
| Meta: I think the very first sentence is the victim of some
| drive-by editing, and needs one more pass. I'm not a native
| speaker, but I still suggest changing
|
| _In an earlier blog post, I showed on the Go programming
| language allow you to write generic functions once you have
| defined an interface._
|
| Into perhaps
|
| _In an earlier blog post, I showed how the Go programming
| language allows you to write generic functions once you have
| defined an interface._
|
| Considering the audience and author, I would seriously consider
| omitting the explanation of what Go is, but that's just polish.
| :)
| psychphysic wrote:
| There are quite a few little language mistakes that I couldn't
| figure out if it was a language thing or just a typo.
|
| > Of course, it also limits to the tools that I use to program:
| they cannot much about the type I am going to have in practice
| within the count function.
|
| I think dropping the 'me' is often a feature of those whose
| native language is eastern European/Russian. The second
| (possibly) missing 'know' seems to support just editing
| mistakes. The structure of both makes me think of Portuguese
| for some reason!
|
| To avoid going off topic on HN, something... something...
| ChatGPT
| planede wrote:
| > So what are concept good for? I think it is _mostly_ about
| documenting your code.
|
| Emphasis mine. While it is somewhat good as documentation,
| sometimes static_assert with a custom diagnostic message is
| sometimes better for this purpose.
|
| The hidden power of concepts/constraints is the way it shapes
| overload sets. You can have something like:
| template <forward_range R> void foo(R&& r) { /*
| some generic algorithm */ } template
| <random_access_range R> void foo(R&& r) { /*
| optimized for random access */ }
|
| and it will work if you pass a random access range, as the
| compiler can deduce that it's more specific than a forward range
| to resolve the ambiguity. Prior to concepts writing code that did
| this was way more cumbersome.
| pornel wrote:
| Rust requires trait bounds (its equivalent of concepts) to
| type-check the template code at definition site, when it's
| written, not at the instantiation site where it's used. This
| results in much better error messages. The downside is that
| trait bounds for numeric code are awfully verbose, and you
| can't sneak in a printf without declaring it as a requirement.
| gpderetta wrote:
| Indeed. I haven't found concepts to be very good at generating
| error messages. But they are great for documentation and to get
| rid of SFINAE hacks for overloading.
|
| The shorter template syntax is a bonus.
|
| edit: concepts should also allow for better IDE tooling, for
| example proper completion inside template functions; although
| it is supposed to work, I didn't notice it firing yet in
| clangd.
| pjmlp wrote:
| They are also great for poor man's reflection, you can
| combine concepts with C++ traits (not the same as Rust's) and
| _constexpr if_ , to check if the given type supports some
| specific capabilities.
| jcelerier wrote:
| In Qt Creator I can see the interface defined by a concept
| upon autocomplete
| Uptrenda wrote:
| [flagged]
| randomint64 wrote:
| Rust is a serious contender is this space, and closing the gap
| quickly.
| pjmlp wrote:
| Including introducing new features on 6 weeks basics, just
| wait until Rust also gets 40 years of history.
| tialaramex wrote:
| It's true, improvements to Rust ship on a six week cycle,
| the next will be Rust 1.69. Nice. I was inspired to improve
| a compiler diagnostic earlier this year+, I benefit from
| that improvement already in the stable compiler today.
| Whereas if you "miss the train" with standard C++ you've
| got three years to wait each time, and of course the Powers
| That Be can ensure that oops, you just missed the train
| again...
|
| Of course Rust's improvements are actually compatible, not
| only by fiat, but because Rust's automation extensively
| tests each of these six weekly releases against the vast
| field of Free Software out there written in Rust. Now maybe
| this is secretly happening for C++ and they're just very
| bad at it. Or, as seems more likely, it's not done, the
| results are the same either way, new C++ versions require
| extensively manual testing to upgrade your software before
| you can take advantage without too much fear.
|
| + Rust knows that characters like 'A' aren't necessarily
| one byte, and it deliberately doesn't coerce them to fit in
| a byte, you'd need to convert them, so let ch: u8 = 'A';
| won't compile. But ASCII characters can fit in a byte, so
| there is syntax to write that b'A'. My change means that
| the compiler will explicitly suggest you modify that
| earlier mistake to let ch: u8 = b'A'; which works, however
| it knows _not_ to recommend nonsense like let ch: u8 = b
| 'PS'; the pound currency symbol isn't in ASCII so you keep
| the same diagnostic just explaining what's wrong with no
| suggestion.
| pjmlp wrote:
| Again, wait until Rust gets 40 years of history
| deployments, distributed from the tiny 8 CPU, to HPC
| workloads and FPGAs, or stuff running on Mars.
|
| I doubt very much that Rust editions and backwards
| compatibility history will be able to survive 40 years
| with such diverse use cases, without introducing
| accidental complexity and corner cases along the way.
|
| This assuming that we can still use Rust and not Crab ,
| as if Rust also doesn't have its own show of politics.
| tialaramex wrote:
| Rust is not designed for 8-bit CPUs like Tiny8. The
| smallest usize is allowed to be is 16 bits.
|
| In practice on these very tiny devices high level
| languages are total overkill. Grace's original "compiler"
| concept makes sense, but today's assemblers are more than
| sufficiently capable. You can _literally memorise_ what
| all the individual memory locations (actually Tiny8 just
| admits they 're registers, it's not as if it would make
| sense to _also_ have registers when you only have 256
| bytes of RAM) are used for which means even the idea of
| _variable names_ is of doubtful value.
|
| I don't know if it's practical to write a conforming C++
| "freestanding" compiler for Tiny8, but I can't imagine
| it'd be any more useful than Rust would be if you did.
|
| The reason there isn't stuff on Mars running Rust is
| mostly that it takes a long time both to get stuff
| approved for that kind of application and to send things
| to Mars. Still I'm sure in 40 years there will have been
| Rust on Mars because why not and I doubt it'll have
| significant impact on Rust syntax.
|
| There already _are_ inelegant decisions which cannot (for
| compatibility) be revoked, but they 're much less
| numerous and egregious at this point in Rust's life than
| similar problems were in standard C++. If you want one to
| point at, for some reason, I suggest comparing ASCII
| predicates like char::is_ascii_lowercase(&self) -> bool
| with the non-ASCII ones like char::is_lowercase(self) ->
| bool
|
| Because char is Copy, the latter design would be more
| elegant, and allows e.g.
| "C++".contains(char::is_uppercase) which is true, whereas
| the ASCII variant means we need the more awkward looking
| "C++".contains(|c: char| c.is_ascii_uppercase()) going
| via a lambda but alas the way we got here didn't allow
| that to happen.
| tialaramex wrote:
| Wait, maybe you meant one of the other "8-bit" CPUs which
| actually have 16-bit address bus? That's kinda cheating
| but yes now we might actually want a programming
| language, we've got all this RAM to play with, we can
| make a stack, we can invent data structures, sure, Rust
| is fine with that setup. Or well, it's crippled, but not
| in any surprising ways you care about.
|
| But it doesn't seem like there are interesting lessons
| here? Running the compiler on this sort of hardware was
| torment (I know, I'm old, I wrote my first software in
| the 1980s for a Commodore Vic 20, _my program source code
| didn 't fit in RAM so my parents had to buy a RAM
| expansion_) but we just wouldn't do that today, we can
| cross compile from say, a Raspberry Pi, or even a real
| computer.
| pornel wrote:
| You know this is a red herring. Frequency of the release
| cycle is orthogonal to the amount of changes or even how
| long the changes are in development.
| pjmlp wrote:
| Just wait until Rust gets 40 years old.
|
| Pity I won't be no longer around to check on it, given
| average human life expectancy.
| pornel wrote:
| I can't wait for C++64.
|
| But "just wait until Rust will repeat C++'s mistakes" is
| just pure speculation. Language evolution doesn't have to
| make the language worse. Java, JS, C#, or Ada are pretty
| old now, and have been doing fine. Rust is well prepared
| for a 40-year lifespan with its edition system.
| pjmlp wrote:
| You quite clearly are unware of the evolution pain points
| to move past Java 8, .NET Framework 4.8, and how the Java
| community embraces Java 20, or the C# one sees C# 12, and
| the rate they are adding new features.
|
| As for JS, everyone knows the mess of the Web ecosystem
| and frontend development.
|
| Ada is doing just fine, as most vendors are still
| adopting Ada 2012. Ada Core and PTC are the only ones
| with the latest version, from 7 remaining vendors.
| pornel wrote:
| But the ecosystem lagging years behind the latest version
| is a separate problem, and one that ironically the 6-week
| release cycle of Rust helps with: there are no major
| upgrades to fear, and small frequent releases make the
| ecosystem move with the compiler instead of having time
| to ossify and choose to stay on an old version (the same
| way nobody chooses to stay on an old Chrome, but people
| used to stick to good'ol versions of IE and Netscape).
| pjmlp wrote:
| Lagging behind is only one issue, I explicilty mentioned
| the drama of newer updates that make many unconfortable
| given the rate that they now are coming with changes for
| the sake of it, just go read the comments on the C# 12
| features announcements for a taste of it.
| detrites wrote:
| Hard agree. As others remarked it's almost like the unwritten
| rule over time. Do programmers end up with such an attachment
| to a particular language, they prefer to pretend this kind of
| over-iterating isn't happening rather than simply address it?
|
| It's a shame as these things often begin well. Ie, if the
| lesson were instead learned, people might better stick to
| providing features in a rich ecosystem instead of endless
| feature-creeping of the core value-proposition to oblivion.
| pjmlp wrote:
| No different from any programming language with several decades
| of evolution.
| otabdeveloper4 wrote:
| Yeah, I agree, let's just rewrite all our system software in
| Javascript. Much better.
|
| (Well, except for your Javascript interpreter, that will still
| be written in C++, obviously.)
| Maken wrote:
| Why using an interpreter? Just go down the FJCVTZS route and
| make the CPU accept Javascript/WASM as assembly code at this
| point.
| pjmlp wrote:
| It is only a matter of implementing it on a FPGA actually.
| gpderetta wrote:
| Tsk, JS is already the old thing. All the cool kids are on
| Typescript now.
| dxuh wrote:
| It's just like Java or JavaScript or any other language people
| actually use for a long time. And before you know it, Rust will
| be like that too. And every other language you love as well, if
| it manages to gain significant traction.
| midland_trucker wrote:
| > In Go, I found that using an interface was not free: it can
| make the code slower. In C++, if you implement the following type
| and call count on it, you find that optimizing compilers are able
| to just figure out that they need to return the size of the inner
| vector.
|
| I'm surprised at this, do Go interfaces really introduce much
| overhead? Of course this depends on the level of performance you
| care about but surely, being a statically typed language, lots of
| the same optimisations are available.
| [deleted]
| travisd wrote:
| Go generally is pretty conservative about that kind of thing
| (namely, compiler optimizations). Go generally abides by a
| "what you write is what you get" kind of thing, especially when
| it comes to "non-local" optimizations. It's generally opposed
| to anything that's "clever." (Just my feeling as someone who
| uses Go pretty often and who respects the choice they've made
| on that spectrum).
| [deleted]
| perfmode wrote:
| it's actually to keep compile times fast
|
| and for the implementation of the compiler to remain simple
| travisd wrote:
| Yep, I think those things are all related.
| pjmlp wrote:
| The first one is doable in more complex languages, e.g. D,
| Ada, Delphi,...
| MarkMarine wrote:
| Interfaces always go to the heap in go, so yes, you can take
| some simple code that could easily live on the stack and make
| it slower by wrapping it in interfaces with go.
| wahern wrote:
| Interface methods in Go are like virtual methods in C++. In
| principle C++ compilers when statically compiling everything
| can often remove virtual method indirection for some objects,
| but I think in general this optimization is still uncommon and
| difficult to coax out a compiler. Go definitely does not do
| this, even though in principle it should be easier.
|
| Basically, "interfaces" in Go and C++ actually refer to quite
| different language features. (Or at least, the author is using
| the term to describe quite different language features.)
| pjmlp wrote:
| This is common when doing LTO, without it there is no
| guarantee that there isn't some dynamically loaded code that
| would be broken, this is one area where JIT focused languages
| have an advantage.
| gpderetta wrote:
| Indeed you need LTO for generalized devirtualization, but
| guarded devirtualization, static classes and final can
| still help even without LTO.
| perfmode wrote:
| There is overhead in interface indirection because the Go
| runtime needs to perform dynamic dispatch to determine which
| method to call at runtime.
|
| C++ can optimize interface indirection away because it supports
| static polymorphism, which allows the compiler to generate
| specialized code for each concrete type used with a generic
| interface, eliminating the need for dynamic dispatch.
| shilangyu wrote:
| > In Go, I found that using an interface was not free: it can
| make the code slower.
|
| The Go version that was presented isn't equivalent though. In Go
| you are accepting an interface directly which will hide the value
| under some fat pointer for dynamic dispatch, in c++ you are using
| generics to monomorphise the function to specific types. If you
| want to compare the implementations fairly you should've used Go
| generics: func Count[T IntIterable](i T) (count
| int) {
| mirashii wrote:
| Fair criticism, though I do wonder if it'd really make that
| much of a difference. Go doesn't really monomorphize generics
| either, and would end up with an equally if not more expensive
| lookup for the correct generic function at runtime.
|
| Some reading:
| https://github.com/golang/proposal/blob/master/design/generi...
| https://planetscale.com/blog/generics-can-make-your-go-code-...
| bheadmaster wrote:
| That's true at the moment, but still an implementation
| detail. I think I remember early versions of C++ compilers
| doing the same thing with templates.
|
| Considering the progress Go compiler has gone through, I
| think it's reasonable to expect the optimized implementations
| will come few versions down the road.
| rep_movsd wrote:
| C++ templates have never used runtime dispatch
| bheadmaster wrote:
| I assume you've checked the version control history of
| every C++ compiler in existence?
| seanhunter wrote:
| Not the OP, however I have programmed in C++ since 1987
| across many different operating systems and hardware
| platforms and I've literally never heard of a compiler
| that implements template stuff using runtime dispatch.
| CFront3 which was I think the first real template
| implementation that most people used certainly never did
| it that way, neither did any version of gcc, visual
| studio or Sun Workshop, which are the compliers I used
| the most from that period. Dug out my old copy of
| Coplien[1] which is from the early 90s and it discusses
| runtime dispatch in depth in the context of vtables and
| virtual function pointers and the cost of these things,
| so the concept was well understood but not a cost anyone
| was paying with templates.
|
| [1] https://archive.org/details/advancedcbsprogr00copl
| "Advanced C++ Programming Styles and Idioms" aka the
| first programming book that genuinely kicked my ass when
| I first read it and made me realise how good it was
| possible to be at computer science.
| gpderetta wrote:
| It would be extremely hard to implement templates with
| dynamic dispatch while maintaining the correct semantics.
| foxhill wrote:
| templates don't exist after the front end. there is no
| ABI that allows them to exist in any object file. there
| is no object file format they could be embedded in, sans
| a string representation of the source they came from.
|
| _extremely hard_ is underselling it somewhat :)
| seanhunter wrote:
| Right. For starters, from the very beginning C++ has
| supported function templates which take native types. So
| you don't even necessarily have any kind of pointer you
| could add a vtable to even if you wanted to. Then add to
| that the guarantee[1] about pod types being directly
| compatible with C which as you say I don't see how it
| owuld be possible to do.
|
| [1] which has always been strong even before there was an
| actual ISO/ANSI standard
| shilangyu wrote:
| I don't know why I thought Go generics also do
| monomorphization, must've misremembered or it was an earlier
| proposal? Thanks for the correction!
___________________________________________________________________
(page generated 2023-04-19 23:03 UTC)