> In fact, it's possible you haven't realized what really went wrong yet.
Not only is it possible, I think it is very likely, although your explanation is something I can follow along with and very much appreciated.
> There are a few things that went wrong here, but the final red flag to notice is that you should never call a constructor directly with 1 argument directly, because it's just a different syntax for a C-style cast, which we know is dangerous due to its bypassing of safety checks—and this is true regardless of whether we're dealing with C++ constructs (like classes and constructors and such), which I think might be what you're realizing now.
Huh, interesting, I actually did not realize this. Is there a way to do this safely without creating an extra lvalue? I take it that there is no “extra explicit” keyword I can add to prevent this kind of accidental call, is there?
> I'm going to go out on a limb here and say that was another mistake, even though it "solved" your problem here: despite the widespread use, brace initializers are, in my experience, not a good thing
Yeah, I am not really a fan of them either :( Even I know of a bunch of caveats about them and C++ initialization is an extremely complicated topic…
> If you're reading this, you're probably noticing that you ended up with Instruction&& in the first place—that's probably not what you wanted, or at least not what you should've wanted.
No, but as you observed that it “works out” at some point in the pipeline so I obviously did not care to really figure out if this was what I wanted or not.
> And that's where we get to the heart of the issue: your real problem is that you used decltype. If I saw that during code review, I would force you to change it—and using it on declval is just adding more fuel to the ember.
Somewhat strangely, C++ seems like the only language where I would even consider to use such a construct. I think every other language just erases their types or simplifies them so you can be comfortable writing something like “Iterator i = collection.start” or “int size = collection.count” whereas in C++ you have some generic distance_type and it feels dirty to just work with a size_t or whatever you know the thing to be.
> IMHO you should only use the newer features if they solve an actual semantic problem for you, not merely because they minimize your typing.
A good point, but I would like to just mention that this was clearly an experiment in trying out the “latest and greatest” ;)
> Not to mention the slow compilation speed and lack of independent compilability.
Wait, you’re telling me my 100 line program shouldn’t take a dozen seconds to compile?!
> Is there a way to do this safely without creating an extra lvalue?
The static_cast<T>(arg) syntax I used does exactly this! It's what you should use pretty much everywhere instead of T(arg). If it's too much typing, yeah unfortunately it is, though life is a lot easier if you can e.g. bind 'sc' to expand to it in your editor.
> No, but as you observed that it “works out” at some point in the pipeline so I obviously did not care to really figure out if this was what I wanted or not.
Yeah... sadly C++ is just about the 2nd-to-last last language you should deal with like that. The last probably being C. :-) Pro tip that might make it easier to avoid this: use typedefs very liberally. They help you avoid auto/decltype/etc. and are quite robust. (At least if your reviewers let you. If they don't, they probably haven't learned it the hard way yet.)
> Somewhat strangely, C++ seems like the only language where I would even consider to use such a construct. I think every other language just erases their types or simplifies them so you can be comfortable writing something like “Iterator i = collection.start” or “int size = collection.count” whereas in C++ you have some generic distance_type and it feels dirty to just work with a size_t or whatever you know the thing to be.
Those languages break too actually. Go Google "binary search bug" (with quotes). For example in C# there's Length and LongLength, which is dirty. When what they really need is just a native int. Another C++ tip: almost every 'int' or 'unsigned int' you ever deal with should be size_t or ptrdiff_t, because at some point or another it's probably an array index. It's very rare for that not to be the case; the only case I can think of off the top of my head is a logarithm (i.e. the shift amount in a bit-shift expression) or a timestamp (long long). Unless you're writing a generic STL-like container or allocator type (in which case, best of luck...), you won't need to care about difference_type or size_type.
> A good point, but I would like to just mention that this was clearly an experiment in trying out the “latest and greatest” ;)
Not only is it possible, I think it is very likely, although your explanation is something I can follow along with and very much appreciated.
> There are a few things that went wrong here, but the final red flag to notice is that you should never call a constructor directly with 1 argument directly, because it's just a different syntax for a C-style cast, which we know is dangerous due to its bypassing of safety checks—and this is true regardless of whether we're dealing with C++ constructs (like classes and constructors and such), which I think might be what you're realizing now.
Huh, interesting, I actually did not realize this. Is there a way to do this safely without creating an extra lvalue? I take it that there is no “extra explicit” keyword I can add to prevent this kind of accidental call, is there?
> I'm going to go out on a limb here and say that was another mistake, even though it "solved" your problem here: despite the widespread use, brace initializers are, in my experience, not a good thing
Yeah, I am not really a fan of them either :( Even I know of a bunch of caveats about them and C++ initialization is an extremely complicated topic…
> If you're reading this, you're probably noticing that you ended up with Instruction&& in the first place—that's probably not what you wanted, or at least not what you should've wanted.
No, but as you observed that it “works out” at some point in the pipeline so I obviously did not care to really figure out if this was what I wanted or not.
> And that's where we get to the heart of the issue: your real problem is that you used decltype. If I saw that during code review, I would force you to change it—and using it on declval is just adding more fuel to the ember.
Somewhat strangely, C++ seems like the only language where I would even consider to use such a construct. I think every other language just erases their types or simplifies them so you can be comfortable writing something like “Iterator i = collection.start” or “int size = collection.count” whereas in C++ you have some generic distance_type and it feels dirty to just work with a size_t or whatever you know the thing to be.
> IMHO you should only use the newer features if they solve an actual semantic problem for you, not merely because they minimize your typing.
A good point, but I would like to just mention that this was clearly an experiment in trying out the “latest and greatest” ;)
> Not to mention the slow compilation speed and lack of independent compilability.
Wait, you’re telling me my 100 line program shouldn’t take a dozen seconds to compile?!