jacobnavia writes:
> As you see the "get" template receives an index that IS HARDCODED TO THE
> INDEX OF THE FIELD, and any changes by INSERTING A NEW FIELD will make those
> indexes silently access the WRONG FIELD,
Which will now be a completely different class, and this will guarantee a
compilation error, until this is fixed.
A new field will change the size of the tuple binding.
This means that all existing code that uses structured bindings for these
values from the function that returns it, like:
auto &[name, age]=some_function_that_returns_UserEntry();
If and when a third field gets added to this UserEntry, and gets exposed via
structured bindings, this specific binding will now FAIL. Guaranteed. No
chance of miscompilation. Even if the name field is now index #1, and age is
index #2 and the new field is the same data type as name, and index 0.
So, there are now at least two different reasons why this will cause a
compilation error, and why the original claim is still 100% bullshit.
> since if you add a field at the
> beginning, it will become the zeroeth field of course.
>
> Will it compile?
No.
> Well, if you insert a std::string at the beginning it WILL COMPILE since the
> type of the first field is unchaged: it was std::string and REMAINS
> std::string.
None of the existing structured bindings will compile. They're all binding
two elements, instead of three.
Fail.
But what about pre-C++17 code that uses `std::get` to bind individual
elements?
Well, as the old joke goes: "Doctor, it hurts when I move my arm this way".
"Well, don't move your arm this way, then".
With a
auto &[value1, value2]=
structured binding, you are guaranteed a compilation failure when a new
binding gets added or removed. That's one preventative measure that's the
simplest solution. Use structured bindings this way, and any new bindings is
a guaranteed compilation failure.
Now, what about code that uses "std::get<>" indexes directly? Perhaps you
were only aware of the std::get<>-based version of structured bindings, and
you weren't aware of this, C++17 structured bindings?
That's ok. A reliable solution is still simple. Let's say you wanted, for
whatever reason, for a new std::string to take place of the existing
std::string name that std::get<0> gives you, in this situation.
Of course, the easiest solution is to return the new value as std::get<2>, a
new binding. But let's say you're stubborn and want to add a new one as
std::get<0> and shift the rest. And you want to guarantee a compilation
error?
Well, since you haven't yet written any new code that will use this new
field, you just declare one that's guaranteed to miscompile with any code
that uses the existing std::get<0> in any observable way:
class surname_t {
public:
surname_t(const surname_t &)=delete;
surname_t &operator=(const surname_t &)=delete;
};
Now, define this as your new std::get<0> binding. Compile the end result,
and have your compiler find all the existing usage of std::get<0> for you to
fix. It's going to be a bit difficult to assign it to a std::string. Even
with
auto name=std::get<0>(object);
Whatever subsequent code uses it next will fail. At some point, anything
that expects a std::string here will not find it. Only if this name isn't
used anywhere will let this slip through. But who cares, in that case.
It also seems to me that defining a
template <size_t I> auto get(const UserEntry& u) {
// …
}
is not required. One can simply declare a
template <size_t I> auto get(const UserEntry& u);
and then simply specialize it:
template<> auto get<(size_t)0>(const UserEntry &u)
{
return u.getName();
}
and so on, for the rest.
Now, one can easily respecialize that one as get<1>, leaving get<0>
completely unspecified, and thusly find all existing code that refers to it.
This will guarantee that no references to std::get<0> from existing code
will remain.
Now, you may find some existing code, perhaps something that uses
std::apply, that will now completely fail. But it'll need changing in any
case, to handle the new binding properly, so you can temporarily stub it
out, and put it back when the binding is completed.
And once now the existing code compiles, you've proven that you have no code
that uses the former std::get<0>, as a std::string, or anything else, so now
you're free to bind std::get<0> to an actual, new, std::string, with no
possibility of overlooking some existing reference.
But, even better, you should take this as an opportunity to find every
existing instance of direct usage of std::get<> (since std::get<1> of age,
that was getting an int before, will now be seeing a std::string, and fail),
and replace them with proper structured bindings:
auto &[surname, name, age]=whatever().
I would expect every modern compiler to completely optimize away whichever of
these are not actually used, so they come as cost-free, and future additions
to the structurally-bound class will not require a wrapper class, like this.
> Since you told me I do not know C++ I will leave to you the answer to that
> question, since you obviously know more C++ than me.
Yes, it does seem that I do. I have done precisely things of this nature
safely, and with 0 resulting defects. You just have to understand that these
kinds of breaking API changes should be done in a way that guarantees a
compilation error from any existing code that needs to change, and then have
the compiler find every occurence that needs changing, for you.
It is not difficult to cause a C++ compilation error in existing code. One
of the easiest things to do, in C++.
It is true that you just can't rebind, in this specific example, of
std::get<0> directly to std::string, and rely on the human factor to hunt
down all existing usage of it. You have to force a compilation error, and
have the compiler do it for you. That's what you will need to do, and if it
takes any amount of effort to do it, you make sure that the next time you
have to do it, it won't take much effort at all.
Now, perhaps you can prove that you know more than me here, and think of
some situation where a stub wrapper like this will not cause a compilation
error, but will still have observable changes. But, I'm pretty sure I can
come up with something that will also force a compilation error, in that
case.
> What has been gained by adding this enormous complexity?
The need to avoid declaring a bunch of helper classes, for one. Structured
bindings are not simply about declaring specialization of std::get. It's
much more than that. For example, if one needs to have a function or a
method return two discrete values, no need to declare a new helper class,
just a
std::tuple<int, const char *> so_long()
{
return {42, "And thanks for all the fish"};
}
And then simply get two natural objects from
auto [answer, mostly_harmless] = so_long();
Suddenly, it now becomes much easier for functions to actually return two,
or more, objects, without wearing out one's keyboard.
And by following this model, if so_long() ever needs to return three values,
just do it, and your C++ compiler will find all callers, guaranteed.
> That is my question, that remains. I hope that I answered yours:
>
> YES, THE CODE WILL SILENTLY COMPILE WRONG STUFF.
Sorry. That's still 100% bullshit. It is not difficult to prove that no
existing code will "compile wrong stuff". Not if you actually know C++, and
know how to use it correctly. Now, can you please pay attention, when
someone's teaching you C++?
If you'd like to prove me wrong, give me an example of an existing
structured binding, its existing usage, in some way, and tell me to rebind
the usage to a different one, and I'm confident I'll find a way to change
the binding, temporarily, to make the existing usage ill-formed, and not
compilable.
Nobody ever claimed that rebinding an existing structured binding can be
done in one step, with guaranteed error-free results. Just that there's
always a way to find all existing usage of structured bindings, and force a
compilation error until it's fixed to use the new binding, even a temporary
one, a wrapper of some sorts, that can be quickly dropped after all existing
code is converted.
In my experience, the worst case scenario is where I had to force a
compilation error twice: once in all existing code that used structured
bindings, converting it to temporary scaffolding; and a second time after
the temporary scaffolding gets removed, and the new structured binding gets
put in place.
But there's always a guaranteed way to get a compilation failure, and not
"SILENTLY COMPILE WRONG STUFF" (see, I can use the CAPS LOCK key too).