template <typename U>
using func_type = decltype(U{}.func());
I admit I'm not following all the new stuff that is added to C++. What are that two lines supposed to mean? I don't know what the U{}.func() and using x = decltype() constructs should mean or their names.
The names of these and links highly appreciated. Thanks.
Moreover, regarding the "bug": if "U" is actually special as an identifier (e.g. some predefined macro or something) it's a pity that the author hasn't investigated a little more. Even not knowing the meaning of the lines I've quoted, the symptoms look somewhat familiar to me with these "special behaviors" of identifiers.
Didn't you read the rest of the article? They tested with arguments with different names. Further they showed that T was accessible from within the other template when it didn't have a template parameter T, making this even more bizarre.
Also, just to be completely sure, I tested it myself.
I know that’s a detail, but if I’m not mistaking the author is only one person, not sure why you wrote your comment in plural form (that personally slightly confused me).
I’m not a native English speaker myself, and I don’t live in a place with a majority of native speakers, so maybe that’s why. I got confused reading your comment as I thought that you were talking about something that I missed from the article (a link, or something else), thus I shared this feedback.
Though I’m not sure why people downvoted my comment. I haven’t expressed anything against the use of neutral pronouns (though that’s unexpected when the author is clearly a man). And I clearly specified that was a detail, not something to give importance to.
FWIW as a native English speaker I didn’t even notice that they (hah) used they. It sounds natural when you don’t know (or don’t care to emphasize) the gender of the singular person in question. Just trying to be helpful for the next time you notice that construct :)
Sheesh, I'm not exactly obsessed with internet points but I am kinda curious why I got so downvoted. I wasn't really trying to be condescending, but the article kinda does answer this concern (regarding U being reserved) in a couple ways.
At risk of being downvoted, myself, as this is off-topic, but since there is no 'meta' and you're clearly looking to understand what happened, I suspect the issue originates in the first statement made from the original comment, which was posed in the form of a question:
Didn't you read the rest of the article?
My rule of thumb is that if a question can have ", moron?"[0] appended to it and both the meaning and tone remain unchanged, then it's going to be taken as condescending. You'll probably find that people who are particularly sensitive to keeping Hacker News a "civil place" (among which I count myself) will read that sentence and skip the rest of the comment before hitting the downvote button. Had it been left off, the rest of your comment offered clarity to anyone who simply skimmed the article or otherwise wasn't understanding what was being presented and might have missed that point. Removing the sentence also doesn't affect the information being conveyed[1].
While I'll take you at your word that it wasn't meant as condescending, a variant of Hanlon's Razor[2] applies which I had shared with me as "Where there are gaps of understanding, people tend to jump to the worst possible conclusion." and this serves as a pretty good example. You assumed the reader didn't bother to read the article, when it was equally likely that they simply didn't understand what was being presented -- a fair estimate since this is C++ templates, which is not something everyone encounters in their day-to-day. And folks who are able to downvote likely did so because they assumed you were being condescending, when it's equally likely that you may have simply written the comment hastily or meant the question in a less-sarcastically sounding manner -- a fair estimate since verbal queues are missing from written text and most of us type near the speed that we think.
[0] Or "dumba$$" or "?, God!" (followed by a gesture of hands being thrown down in disgust)
[1] I once had a great manager who, at a yearly feedback session, told me rather bluntly that I am too verbose and worked with me on how to convey a message by learning what is unnecessary for conveying it. As you can tell if you glance through my comment history, the lesson was entirely lost on me. While I'm much better at eliminating things that add negativity to my intent (often by adding more to my statements), I'm pretty terrible at identifying when brevity has higher value than precision and completeness. Though, when it really matters, I can pull it together if I take enough time... and on and on and on.
As you say, "kinda." Obviously, the way I understood it, it kinda doesn't. It does make some simple tests, but fails short from concluding (or clearly stating) anything specific in the text of the article. The way I read the article, it still appears to me that there are some specific names active in the scopes used, the way they are used, and that author's code hints at them.
decltype is just returning the result type of the contained expression, which is the return type of the method 'func' called on a default or value initialized instance of type U. A lot of times people will use std::declval<U>() instead which returns a reference to U w/o needing to call a valid constructor (https://en.cppreference.com/w/cpp/utility/declval).
The templated using statement is creating a template type 'func_type<U>' which is similar to a typedef.
I can't guarantee I'm right, but AFAIK that's sort of a templated typedef, where the bound type is that of the return value of method "func" of class U. Short example:
class Foo {
int func();
};
// func_type<Foo> = int
template<class T>
using Vec = vector<T, Alloc<T>>; // type-id is vector<T, Alloc<T>>
Vec<int> v; // Vec<int> is the same as vector<int, Alloc<int>>
That explains to me the using construct.
And tcbawo's answer tries to address the U{}.func()
However "either of which could apply to U{}" ... I'd expect that it should be possible to say which of these is in this example? I also can't see how that can (or should?) match "aggregate_initialization."
The expression is defining a type named func_type whose type value corresponds to the return type of the member function named func() in the given type.
Using allows for type aliases like typedef but with a more readable syntax. Decltype will take the type of the expression within. So the U{} constructs a value of type U and then the func() is called. So func type will either be complete when the struct has a func member function or not. The void_t is always void but only if the template arguments are complete. So when func exists it will be a true type else a false type. True and false type are integral constants that have a constant value member
U is a class name, which is given as a template parameter. The braces are the new uniform initialization syntax, which basically means U{} constructs an object of type U. U{}.func() then calls the member function with the name "func" on the constructed object. decltype() returns the type of the contained expression. So I think it should return the return type of the member function. Then "using" assigns that type to an alias, as some typedef.
I tried remember reading an article on HN a couple of years ago about how MSVC got initial C++ support and added more and more features over time, despite having not being designed with C++ in mind originally. The result was a combination of clever hacks after clever hacks, with the consequence being the reputation MSVC got over time (I'm not saying this is one the consequence shown in the OP)
Anyway, does anybody still have this article somewhere? I couldn't find it anymore :(
MSVC has had C++ support for a long time, to be fair. GCC was probably just as bad at first, it just improved more steadily over time. Today, GCC and Clang both seem like very robust C++ compilers, and it doesn't feel like MSVC is terribly far behind (it does feels like it is behind, in my perception, just not far.)
Well, and Android and iOS and many microcontrollers (well, often a shitty GCC fork in that case but still) and way more platforms (OpenBSD, Minix, HP-UX, Solaris, VxWorks, bare metal) and architectures (Alpha, ARM, AVR, x86, m68k, microblaze, MIPS, PDP-11, Risc-V, SPARC, VMS) than that, not including third party ports in SDKs.
Yeah, I get that with embedded, it's a crapshoot. However it's probably not ridiculous to say that GCC ports to microcontrollers is not terribly uncommon, either.
GCC and Clang certainly are a lot more useful than for just that tiny range you listed. The reality is they probably cover more platforms than any other individual compiler.
I'm a two decade Windows developer so this isn't a "M$ Sucks" comment, but Windows itself is a series of hack on top of hack because Microsoft refuses to break backwards compatibility.
As much as MS had the reputation of "Windows isn't done until Lotus won't run" back in the day. If you follow some of the MSDN blogs, you'll find the extraordinary hacks that MS has done over the years to keep the most obscure programs running.
People complain about how Apple will drop support for backwards compatibility but this a consequence of the MS culture of doing just the opposite.
There’s a very good business in maintaining backwards compatibility. Any large enterprise is not going to buy from a company that they can’t rely on having a roadmap that will fix security issues but won’t break their deployed software.
Unfortunately the market for "companies that need to deploy a fleet of Windows computers to run a bespoke app" is shrinking compared to the entire market. I'm thinking UWP is a them attempting to start over.
Most software on Linux is backward compatible as well, you can get some 90s software, compile it and run it, however it does not have as many hacks as Windows has at the moment to achieve that.
you can get some 90s software, compile it and run it,
The "compile" is the tricky part; with Windows, you can get a binary from the early 90s and it will just run. My experience with Linux software is that while the kernel-userspace interface remains stable, userspace is itself full of breaking changes, and software has so many dependencies that trying to compile it quickly turns hairy.
I must admit you are right, it works again on Linux because the API is stable and you have the source, if you take a Linux binary directly from the 90s, there's very little chance it's going to run properly..
Linux has a stable kernel ABI. If you can get hold of the appropriate libraries of the vintage of the executable and set up the library loading correctly, it is possible to run it. Of course if the binary is a.out format, then we can't do this.
Compat in windows was a highly engaged technical problem, in a way that would be considered ‘hack on top of hack’ in linux.
Windows ability to shim is significantly more sophisticated then maintaining a stable abi. It can present a facade of old implementation details to specific programs. Here is a Raymond Chen blog post (2006) on making an app with a ridiculous bug continue to ‘work’ by creating a decoy to compensate for the app bug. He mentions app compat in the ‘immature’ days of windows 95 where they ‘only’ had the ability to use app compat flags and hot patch binaries in memory on load: https://blogs.msdn.microsoft.com/oldnewthing/20060109-27/?p=...
It can be argued that extreme app compat is a bad thing. Paying customers with line of business apps that ‘simply’ continued working never argued that.
Simcity supported in Win95 by allowing a use-after-free bug (if i am reading it right) to continue working.
You get something similar in Linux by how once a userspace facing API is in the wild, the API stays, bugs and all, in case something, somewhere, use that specific behavior.
A newer API will be introduce alongside the "broken" one however, with future code highly incentivized to use the newer API.
Sadly that level of stringent API behavior is seen not as a virtue but as a burden by higher layer devs. Often to the point that the refuse to work with Torvalds after being berated for their lax attitudes, and dream of the day he retires...
Can't find the site again, but someone figured out you can use an older version of the Borland resource editor to convert the embedded resources to a newer format, than patch the exe header to say it's a windows 2.0 binary and it will run even on windows 10 (32bit version only since of obviously needs the 16bit subsystem).
> 32bit version only since of obviously needs the 16bit subsystem
And afaik, MS only retired the 16-bit subsystem because AMD64 can't switch between 64 and 16 bit modes. You can either run it in 16/32 or 32/64 modes, but not 16/32/64.
The tricky part is that a LDT entry also contains flags which define if a segment contains 16 bit, 32 bit or 64 bit code. If you change to a segment, the CPU will automatically start interpreting the code inside the segment according to this value.
In other words, 16, 32, and 64-bit code can all coexist simultaneously and the CPU can switch between those automatically --- MS just decided not to implement it. Linux with WINE does, however.
The following is a video of someone installing Doom II on DOS, upgrading the operating system in-place 11 times, and running Doom on Windows 7 (around 24 years of upgrades).
Yeah userspace mess is one i lament getting to know, because it has soured me on the whole Linux-on-desktop thing.
Frankly that part is a large contributor to "consumer" Linux never got any traction beyond Google going all out and making their own (Android and ChromeOS), while the DE people keep "polishing" the UX by introducing yet more dependency kudzus.
One example on the top of my head is udisk. First there was udisk1, that had a CLI interface as well as a dbus api. Then came udisk2 that trashed the CLI interface and introduced a new dbus api. And now there is probably some equivalent of udisk3 living inside systemd that has yet another dbus api.
And all this for what? Mounting and unmounting removable media...
In most places I worked for it actually seemed to have the reputation of a fairly good compiler toolset, unfortunately with just a bit of non-standardness sprinkled on top of it, a bug here and there, and perhaps not the fastest out there. (Which describes pretty much every other compiler out there as well: it's not like there's a perfect one with no quirks whatsoever). But it does the job, the optimization is ok as well, and it usually comes integrated in VS which is top of the line compared to other IDEs.
I've crashed MSVC 15.7 repeatedly while writing templates - usually due to an error in my code, but sometimes on valid code. I really do appreciate that Microsoft has been developing their C++ compiler a lot faster than in the past, but this seems like a buggy release.
I found years ago that (at least with older versions) VC++ implemented templates essentially as internally generated macros. All the non-standard behaviour they had could be explainined by considering how they would act if they were macros. This bug strikes me as being consistent with that. T is leaking across structs because it is internally #defined and seen by another struct.
I think Microsoft has managed to prop up this massive template-as-macro hack all the way through VS 2017.
"MSVC previously recorded the body of the template as a stream of tokens and stored that stream away to be replayed at instantiation time. The behavior of MSVC’s template substitution from a recorded token stream somewhat resembled the behavior of macro substitution in that limited analysis was done of a template’s body."
As someone who is admittedly not an expert in template metaprogramming by any means, thinking of templates as an extension of macros feels like the intuitive and simpler way compared to the more complex two-phase algorithm, so it's no surprise that MSVC did it too... then again, I prefer simpler languages (like C) and see the difficulty of implementing the standard as a sign that its complexity may be too high.
There's a reason why templates work like this. If they didn't work like this then template specializations that you write can break somebody else's code which is dependent on the template behaving a certain way.
60 comments
[ 3.6 ms ] story [ 67.1 ms ] threadThe names of these and links highly appreciated. Thanks.
Moreover, regarding the "bug": if "U" is actually special as an identifier (e.g. some predefined macro or something) it's a pity that the author hasn't investigated a little more. Even not knowing the meaning of the lines I've quoted, the symptoms look somewhat familiar to me with these "special behaviors" of identifiers.
Also, just to be completely sure, I tested it myself.
Using X: https://godbolt.org/g/B9YEgZ
Showing the template parameter leaking with neither T nor U: https://godbolt.org/g/CKe2wG
Though I’m not sure why people downvoted my comment. I haven’t expressed anything against the use of neutral pronouns (though that’s unexpected when the author is clearly a man). And I clearly specified that was a detail, not something to give importance to.
While I'll take you at your word that it wasn't meant as condescending, a variant of Hanlon's Razor[2] applies which I had shared with me as "Where there are gaps of understanding, people tend to jump to the worst possible conclusion." and this serves as a pretty good example. You assumed the reader didn't bother to read the article, when it was equally likely that they simply didn't understand what was being presented -- a fair estimate since this is C++ templates, which is not something everyone encounters in their day-to-day. And folks who are able to downvote likely did so because they assumed you were being condescending, when it's equally likely that you may have simply written the comment hastily or meant the question in a less-sarcastically sounding manner -- a fair estimate since verbal queues are missing from written text and most of us type near the speed that we think.
[0] Or "dumba$$" or "?, God!" (followed by a gesture of hands being thrown down in disgust)
[1] I once had a great manager who, at a yearly feedback session, told me rather bluntly that I am too verbose and worked with me on how to convey a message by learning what is unnecessary for conveying it. As you can tell if you glance through my comment history, the lesson was entirely lost on me. While I'm much better at eliminating things that add negativity to my intent (often by adding more to my statements), I'm pretty terrible at identifying when brevity has higher value than precision and completeness. Though, when it really matters, I can pull it together if I take enough time... and on and on and on.
[2] https://en.wikipedia.org/wiki/Hanlon%27s_razor
The internet is a fickle mistress.
edit: Nevermind, I can't. Guess the edit threshold is somewhere under 2 hours. Oh well.
As you say, "kinda." Obviously, the way I understood it, it kinda doesn't. It does make some simple tests, but fails short from concluding (or clearly stating) anything specific in the text of the article. The way I read the article, it still appears to me that there are some specific names active in the scopes used, the way they are used, and that author's code hints at them.
Please don't insinuate that someone hasn't read an article.
[...]
Please don't comment about the voting on comments.
Either of which could apply to 'U{}'.
decltype is just returning the result type of the contained expression, which is the return type of the method 'func' called on a default or value initialized instance of type U. A lot of times people will use std::declval<U>() instead which returns a reference to U w/o needing to call a valid constructor (https://en.cppreference.com/w/cpp/utility/declval).
The templated using statement is creating a template type 'func_type<U>' which is similar to a typedef.
And tcbawo's answer tries to address the U{}.func()
However "either of which could apply to U{}" ... I'd expect that it should be possible to say which of these is in this example? I also can't see how that can (or should?) match "aggregate_initialization."
Anyway, does anybody still have this article somewhere? I couldn't find it anymore :(
http://en.cppreference.com/w/cpp/compiler_support
And the table lacks most embedded compilers.
But yes, embedded compilers are usually a nightmare.
Yeah, I get that with embedded, it's a crapshoot. However it's probably not ridiculous to say that GCC ports to microcontrollers is not terribly uncommon, either.
GCC and Clang certainly are a lot more useful than for just that tiny range you listed. The reality is they probably cover more platforms than any other individual compiler.
As much as MS had the reputation of "Windows isn't done until Lotus won't run" back in the day. If you follow some of the MSDN blogs, you'll find the extraordinary hacks that MS has done over the years to keep the most obscure programs running.
People complain about how Apple will drop support for backwards compatibility but this a consequence of the MS culture of doing just the opposite.
The "compile" is the tricky part; with Windows, you can get a binary from the early 90s and it will just run. My experience with Linux software is that while the kernel-userspace interface remains stable, userspace is itself full of breaking changes, and software has so many dependencies that trying to compile it quickly turns hairy.
Windows ability to shim is significantly more sophisticated then maintaining a stable abi. It can present a facade of old implementation details to specific programs. Here is a Raymond Chen blog post (2006) on making an app with a ridiculous bug continue to ‘work’ by creating a decoy to compensate for the app bug. He mentions app compat in the ‘immature’ days of windows 95 where they ‘only’ had the ability to use app compat flags and hot patch binaries in memory on load: https://blogs.msdn.microsoft.com/oldnewthing/20060109-27/?p=...
It can be argued that extreme app compat is a bad thing. Paying customers with line of business apps that ‘simply’ continued working never argued that.
Simcity supported in Win95 by allowing a use-after-free bug (if i am reading it right) to continue working.
You get something similar in Linux by how once a userspace facing API is in the wild, the API stays, bugs and all, in case something, somewhere, use that specific behavior.
A newer API will be introduce alongside the "broken" one however, with future code highly incentivized to use the newer API.
Sadly that level of stringent API behavior is seen not as a virtue but as a burden by higher layer devs. Often to the point that the refuse to work with Torvalds after being berated for their lax attitudes, and dream of the day he retires...
Can't find the site again, but someone figured out you can use an older version of the Borland resource editor to convert the embedded resources to a newer format, than patch the exe header to say it's a windows 2.0 binary and it will run even on windows 10 (32bit version only since of obviously needs the 16bit subsystem).
I was seriously impressed when I saw that.
And afaik, MS only retired the 16-bit subsystem because AMD64 can't switch between 64 and 16 bit modes. You can either run it in 16/32 or 32/64 modes, but not 16/32/64.
Interesting enough you can still mark a code segment as 16 Bit and the CPU will execute it in 16 Bit mode.
https://www.wine-staging.com/news/2016-02-10-blog-wine-16bit...
The tricky part is that a LDT entry also contains flags which define if a segment contains 16 bit, 32 bit or 64 bit code. If you change to a segment, the CPU will automatically start interpreting the code inside the segment according to this value.
In other words, 16, 32, and 64-bit code can all coexist simultaneously and the CPU can switch between those automatically --- MS just decided not to implement it. Linux with WINE does, however.
https://www.youtube.com/watch?v=vPnehDhGa14
Frankly that part is a large contributor to "consumer" Linux never got any traction beyond Google going all out and making their own (Android and ChromeOS), while the DE people keep "polishing" the UX by introducing yet more dependency kudzus.
One example on the top of my head is udisk. First there was udisk1, that had a CLI interface as well as a dbus api. Then came udisk2 that trashed the CLI interface and introduced a new dbus api. And now there is probably some equivalent of udisk3 living inside systemd that has yet another dbus api.
And all this for what? Mounting and unmounting removable media...
Ironically Sutter was saying in C++ [sic] "we have a good backwards compatibility story". :)
https://blogs.msdn.microsoft.com/vcblog/2017/09/11/two-phase...
https://blogs.msdn.microsoft.com/vcblog/2015/09/25/rejuvenat...
In most places I worked for it actually seemed to have the reputation of a fairly good compiler toolset, unfortunately with just a bit of non-standardness sprinkled on top of it, a bug here and there, and perhaps not the fastest out there. (Which describes pretty much every other compiler out there as well: it's not like there's a perfect one with no quirks whatsoever). But it does the job, the optimization is ok as well, and it usually comes integrated in VS which is top of the line compared to other IDEs.
https://gist.github.com/jwilk/b9f9142f204dfa634d3ec338af9172...
I think Microsoft has managed to prop up this massive template-as-macro hack all the way through VS 2017.
https://blogs.msdn.microsoft.com/vcblog/2017/09/11/two-phase...
"MSVC previously recorded the body of the template as a stream of tokens and stored that stream away to be replayed at instantiation time. The behavior of MSVC’s template substitution from a recorded token stream somewhat resembled the behavior of macro substitution in that limited analysis was done of a template’s body."
Fucking disgraceful.