On Thu, 22 Sept 2022 at 18:48, Sébastien Bini <sebastien.bini@gmail.com> wrote:
On Wed, Sep 21, 2022 at 4:51 PM Edward Catmur <ecatmur@googlemail.com> wrote:
std::decompose would need to be magic (compiler-implemented), to be able to call destructors on nonrelocated subobjects, so it would be able to bypass access checks anyway; it might be possible to implement it in the language with reflection once that arrives, but it's widely expected that reflection will also allow bypassing access checks.

So the danger is actually that std::decompose would be too powerful and allow arbitrary code to access private/protected base subobjects of unrelated class types; but this can be prevented by requiring the compiler to look at the context of the call to determine whether the caller is allowed to access that base class subobject.

I fail to see the real danger here. Sure we may extract private/protected subobjects, but the source object we extracted them from is destroyed. So it's not like we can mess with it. We simply get independant objects that have no relationship with one another, which is guaranteed by the fact that the object we extracted them from has no user-provided constructors and destructor.

A tuple class is highly likely to have user-provided constructors, even if its destructor is defaulted; indeed, a class that needs to make use of this facility will almost certainly not be aggregate constructible, otherwise it would be able to use aggregate structured binding. For example, std::tuple has user-provided constructors, so this facility would have to accept class types with user-provided constructors. So there is a definite danger that this could be abused to access private/protected bases of a class type that uses those bases to maintain invariants, and by doing so break those invariants.

Sorry, I meant to use decltype(auto) with no parameter packs. More like:

void vector<T>::push_back_impl(decltype(auto) val);
void vector<T>::push_back(T const& val) { push_back_impl(reloc val); }
void vector<T>::push_back(T&& val) { push_back_impl(reloc val); }
void vector<T>::push_back(T val) { push_back_impl(reloc val); }

Yes, that would certainly be legal and a valid implementation technique.

Every operator token is currently a keyword; there are four identifiers with special meaning (final, override, import and module) but they are not operators.

The problem with having it as an operator that is not a keyword is parsing; is `reloc x;` a discarded-value relocation expression (destroying x) or is it the declaration of a variable `x` with type `reloc`? If `reloc` is not allowed as an identifier, then that's basically the same as making it a keyword.

Fair point. When I first thought of this reloc operator, I considered using a new symbol instead. Like `$x`, `@x`, or `>x`, etc... instead of `reloc x`. However I do find `reloc` to be better than a symbol: first, it clearly conveys the intent. Second, it is visually more eye-catching, which helps when reading code. I reckon it's important to quickly see where relocation happens as it ends the scope of variables.

Yes, but adding a keyword that could be already in use as an identifier is fraught, and could become an obstacle. An alternative is to construct a sequence of punctuation tokens that cannot currently appear in a program, as was done for spaceship; with that in mind, you might propose something like `<~< x`?

FWIW, C has historically handled this by introducing new keywords with _Reserved names and requiring code to opt-in to friendlier names by #including a header that implicitly #defines the new keyword to the _Reserved name. But that's not been workable in C++ since so much of our code is in headers and so it would leak into code that includes those headers. Perhaps modules would make it possible to opt-into a new keyword.

I figured a simple #if __cplusplus >= 202900L would do the job?

That's enough for new code that wants to be backwards compatible, but it doesn't solve the problem of old code that may already use `reloc` as an identifier.