Why Concepts didn’t make C++17
honermann.net
honermann.net
Perhaps someone indeed has been "widely expecting" a huge new source of complexity in C++'s already very complex static binding rules to help produce better error messages. I personally have never met that someone.
Basically the problem with templates is that there is no check for whether a thing can be satisfy a function/operation till it's fully expanded and it's hard to figure out where the "error" occurred during the template expansion. I thought concepts were meant to be like type classes (and type class limits to generics) in other languages so that the template writer could say that a template parameter has to implement x,y,and z and if the type passed in doesn't put the error at that expansion.
An optional constraint on a feature(templates) otherwise too powerful for good error messages.
As I have said before, there have been shockingly poor design decisions in some of the oldest and most fundamental standard libraries. Little to no work has been done to introduce replacement APIs (or even replacement classes) that would allow the deprecation of stupid features to begin.
Killing anything in any C++ that's actually used is going to be extremely hard. Systems stick around forever. Yesterday I had to write some COM/OLE code because it was the only way to do something.
What could be done however, is finding a coherent best subset of current available options which are defined as "the current", blessed C++. Smaller than C++ as a whole, simpler to fit in your head (hopefully), and setting the rest as deprecated. but still complete and coherent.
Maybe it's already done, in that case it just lacks a name a website and more publicity !
What I would put in that subset:
- constructors/destructors (RAII)
- auto (type inference)
- class with template arguments (no nesting)
- variadic templates for function parameters
What I would remove: - virtual
- rtti
- complex templates (nesting)
- no STL (or something new and/or based on POSIX)
- no private/protected in classes (public by default)
- or remove class (only structures)
- operator overloading
- namespaces
- name mangling (a.b gives a_b)
I imagine a good subset as something that Thorvalds could approve and ideally allow in the kernel.(I wouldn't, but some might.)
if you don't mind, may you please elucidate a few here ? or if you already have a dedicated post may you point to that ? thank you !
https://github.com/gcc-mirror/gcc/blob/bd3f0a53c07086e978ea4...
I'm not sure of the behaviour but there is still a loop...
The C++ std. lib. is one of the worst abominations of over-engineered disasters that I have ever seen in my life - how people even deal with stdout/stdin without tearing their hair out is beyond me. I just go back to printf and friends.
The fact that they had to have no fewer than 6 (or is it 8 now?) string types before they got Unicode right... it's truly a sad state of affairs.
How can anyone look at that and not feel embarrassment to have been part of those design decisions...
I would agree, but I look at c# and every time they introduce a new feature the revamp the standard library just fine.. so C++ really has no excuse. C# went from ArrayList -> List<T> -> IEnumerable<T>, and when async/await was introduced they upgraded the methods that used plain old threads before or just introduced new '{name}Async' methods. C++ is so bogged down in legacy at this point that I would never use it given the alternatives of Rust/D.
Well,there is also wstring, but you should just pretend it doesn't exist.
I was merely pointing out that saying that C++ has 8 string types is flatly wrong.
Also, iostream (buffered stream formatting), has nothing to do with posix read and write (unbuffered byte I/O). A closer match is C fprintf.
- Localized text doesn’t work with "iostream" sequences because one language says "A foo B" and another "B bar A". The entire concept of a sequencer like "<<" was wrong from the beginning but they kept it. All I really expect is a formatting method that accepts tuples. Apple made object formats work in Objective-C simply by adding "%@", and they have a solution for internationalization by embedding numeric order.
- Not only are stream operators awkward to use but they are awkward and vague when customized. For years, until compilers basically hacked in ways to inline "friend" functions, you had to put the entire implementations for these operators in inconvenient places outside of classes. And what does it even mean to "operator <<" an object? For many classes, there are at least two useful streamed-out interpretations: a literal version of the object’s primary value(s) from the user’s viewpoint, and a full state representation for developers. The C++ standard library manages to create a very complex way to do something that is not even sufficient!! Many languages and environments have ways to generate multiple object representations. C++ should deprecate "operator <<" and create more grep-able functions that can produce variations (e.g. std::print_obj<T>(std::ostream&, T&) and std::print_debug<T>(std::ostream&, T&), where a default template implements the 2nd using the 1st).
- The "iostream" classes are full of poorly named/located things. Often there are duplicate values with similar names so you have to remember which goes where and hope mistakes don’t compile (e.g. "f << std::hex" and "f << std::ios::hex" are completely different but the compiler allows both). And heck, "good()" does not have the exact opposite meaning of "bad()"! To this day, I see people screwing up and not handling edge-case stream failures correctly; that is an insane problem to have in a widespread standard class that every programmer should be able to learn correctly (and hasn’t, because the class has so many quirks).
- As others have said, std::string lacks practical international support for sources like UTF-8 (which is ironic considering how the class template tried so hard to be adaptable for whatever they thought strings might need). And std::string does not have any really useful methods, especially after considering internationalization. I would say the least useful feature of std::string is size variance; I would hope for a new UTF-8-only class that just expects to be bytes, using good ideas from modern environments to present views of those bytes to programmers.
- STL algorithms probably should not have been implemented in terms of iterators. For instance, if someone writes a template on "container", "container.find(x)" will not work for both sets and vectors; since "find(container.begin(), container.end(), x)" technically compiles with both, a naive programmer may choose to force that approach in every case and incur unnecessary O(n) complexities. Systems are typically so layered with generics that you cannot possibly know if this type of mistaken slowdown has been forced into your code. Assuming you want anything generic at all here, what you really need in the standard is something of the form "find(container, x)" so there is at least a chance for optimal behavior within the magic.
- Ultimately, I wish that more of standard C++ would simply build on or improve standard C. The core C functions should be first-class, with object-oriented peanut-buttering added later where useful instead of baking functionality only into awkward classes that are found later to have design flaws. Even if the C++ version wants to take advantage of C++ "inline" or std::function<>, etc. it should still start as a function and work toward a class. Functions would also create more opportunities to get away from this insane syntax prediction problem that templates have, where you don’t really know if "x.f()" or "f(x)" is required (or if both are available, or worse if they do different things).
the committee failed to achieve consensus that Concepts, as specified in the TS, has attained sufficient implementation and usage experience to be confident in the current design. Basically, the committee did not say “no” to concepts, it said “not yet.”
[1] http://webcache.googleusercontent.com/search?q=cache:SurLIow...
That's okay, I can wait -- concepts are something I really want to learn more about and understand how they work, what they do, and every new article helps.
http://oaktrust.library.tamu.edu/bitstream/handle/1969.1/ETD-TAMU-2010-05-7823/SMITH-DISSERTATION.pdf?sequence=2&isAllowed=y
As always, if something is correct, it's probably due to the good auspices of my advisor; if something's wrong, I've lost all sense of shame, long since.However if it wasn't for my goal to write portable native code across mobile OSes or the occasional weekend dive into LLVM, I would hardly touch C++ nowadays.
Waiting until C++20 for them, will mean 4 more years for alternatives to slowly erode C++'s usage where its features aren't really that critical for fulfilling the customer's use case.
On the other hand, people are still writing new Fortran code everyday, so maybe those 4 years won't make a difference to those highly invested into C++.
For example, last time I searched for it, most embedded and real time OS compilers were mostly C++11 compliant.
How so? Their use cases seem orthogonal.
From what I understand it is the preprocessor macros that make this really awkward. Personally I would like to see C++ abandon those in favor of a "real" macro system.
If there was some kind of parametric polymorphism in Fortran I would die happy.
Also modules are already on VS 2015 Update 1, just not as final design.
I don't like templates at all, I think it's just an even greater hack than how the preprocessor might be used in C. The only place I have liked macros [essentially] is in Lisp.
Something that bothers me about C++ is when a program might instantiate a template like this: std::vector<int>
I'm sure vectors of ints is quite common. I wish C++ could share that generated object code (not the instantiated template at compile-time). I've heard of `extern template` but I don't think it gets used. Even then I wish you didn't have to tell the compiler to look for external versions of the template instance. I wish the compiler could aggressively look for and link to instances that have the same binary representation - even if they have disparate type information. That kind of analysis would probably be easier if C++ had a defined ABI.
I am not a C++ person, so I am sure I'm wrong more than a few times in this post. I wish C++ had a standard class to represent classes, so then we could construct a very generic object heirarchy like Ruby. I don't just mean support for reflection. I also want partially-defined classes for monkeypatching, but people tend to disagree with that for ethical and performance reasons.
I've been happy to see a lot of languages enable expressiveness/freedom to do whatever the hell you want in the last 10 years - instead of people forcing their mentality of how you should code and think about problems.
</ramble-ramble>
I am happy concepts were not added. The preprocessor &and& templates should die.
Today your wish comes true, because this is something that compilers do and have done for a very long time. Redundant definitions of templated functions which didn't get inlined always get dropped at link time. VC++ and the Gold linker also have a feature called "Identical COMDAT Folding" which will agreesively merge functions with different names but identical bodies, which can merge distinct template instantiations in the case where the concrete type doesn't effect the compiled representation.
extern templates are orthogonal to this whole thing; they're purely a compile speed optimization and have no effect on the end binary after linking.
I wonder how many things aren't aggressively inlined in C++..
---
Are you saying `extern template` is used like a `#pragma once` just for the template? I thought it had a greater effect on linking. :(
My concern is that even if the generated object code for std::vector<int> and a binary-compatible type like std::vector<something> are deduplicated in your codebase, that same object code for that specialized type of vector probably exist in several different programs throughout my system. (as an example)
Has it should be, actually. Freezing inlining algorithms in a language specification hinders future optimization algorithms.
Chandler Carruth has done quite a few interesting talks how the clang inliner works.
The generated object code for thing<int> and thing<double> are likely to not be identical, except for where the types are manipulated. Because thing<int> and thing<double> generate entirely different classes, they can actually be radically different. First, C++'s resolution rules allow them to be actually different C++ code. But even if it reuses the C++ code, because that code then goes through the optimization phases, what comes out the other side may not have the kind of trivial relationship you're thinking of.
I don't think this is unique to C++; it should be true of any language that has non-type erased generics that generates native code. I'm thinking of Haskell, OCaml and Rust - but if people who know those languages and compiler chain better think I'm wrong, please speak up.
Personally, I find C++'s templates to be extremely useful and powerful. I also think they are demonstrably not as nearly as hackish as the C preprocessor, as evidenced by the fact that they actually have knowledge of types. The C preprocessor is just textual substitution, while C++'s templates are far, far from that.
On the other hand, Rust generics are monomorphized at compile time, which is closer to how C++ templates work. (The most important difference is that Rust generics are type-checked at definition time, rather than instantiation time.) Another language implementation that uses compile-time monomorphization is MLton, a whole-program Standard ML compiler.
Ideally, the language should offer the programmer two kinds of polymorphism: (0) second-class, which is less expressive but easier to type-check and more amenable to optimization, (1) first-class, with the opposite tradeoff.
I think GP is talking about something different. Let's say you have two completely unrelated types `a` and `b`, but, for whatever reason, `foo<a>` and `foo<b>` happen to compile to exactly the same machine code. (For a concrete example, say `a` and `b` have the same size and alignment requirements, and say `foo` is `std::vector::reserve`.) Then either the compiler or the linker can “merge” the duplicated code for `foo<a>` and `foo<b>`.
Example: You can have 2 struct types that each only contain an int with the same member name. They would [likely] have the same binary representation. You could then create 2 vector types specialized for each struct type. The code for both vectors would handle the data in the same way - but the compiler would view the vector types as _STRICTLY_ separate types.
I do not think this code would be uniquely shared - the compiler would generate code for both vector types.
It's just a theory I have that this is low-hanging fruit to reduce C++ binary size.
You're less likely to define the same structure and methods of a type in exactly the same way as someone else in C because we don't have the STL (ofc). So it's probably a worse issue in C having a hundred different implementations of a linked list or some array type across your system in each program.
I just think there's an opportunity to aggressively dedupe object code for "representation-identical" types in C++.
Someone further up the chain mentioned this sort of deduplication might be done by the linker, but the code for the duplicated type would still exist in the binary before linking.. An old but probably negligible complaint about C++ is that it creates larger binaries than if one were writing C. I think this is something overlooked because C++ has a focus on type strictness - perhaps the linker has a better way of seeing through abstractions..
I've identified where I think a problem lies.. now I just have to find proof it :3
Unrelated: I wonder if identical libraries that exist in separate filesystem trees (like in Docker) are deduped in memory, or if it's done by inode.
This of course means that compile times are a bit longer, as a given template may be instantiated by the compiler many times, but in terms of run-time and final object size it should be optimal.
I think this behaviour also makes more sense when you think about inlining -- a lot of template code is actually doing something quite simple under the hood, so by having the compiler instantiate your template each time, you potentially allow it to inline the code qithout relying on a LTO, which may not have been feasible when this whole mess was designed.
"Defined ABI" is something of a problem, as C++ is required to work on a variety of current and future hardware platforms, with disparate calling conventions and the like.
Monkeypatching is not something I understand how or why one would attempt in C++, but there's always the option of building yourself a C-style collection of function pointers and swapping them out at runtime. Or, may God have mercy on your soul, Windows COM.
Templates are C++'s metaprogramming feature. Without them there would have to be another means of implementing generics. The syntax is often ugly and occasionally bananas, but they're one of the things that make C++ what it is.
The reason I really want some sort of concept support is because the jumps you have to go through to define a generic function that, for example, takes only a range of some specific Ts are getting ridiculous. Especially if you have multiple overloads.
Also, some of the syntactic sugar of the proposal is nice (implicit templates, concept names instead of auto).