C++ Logo

std-proposals

Advanced search

Re: [std-proposals] decltype can access private and protected

From: Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
Date: Sat, 26 Mar 2022 11:19:25 -0400
On Fri, Mar 25, 2022 at 2:27 PM Giuseppe D'Angelo via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> On 25/03/2022 18:46, Frederick Virchanza Gotham via Std-Proposals wrote:
> >
> > Normally the behaviour is undefined when you dereference a nullptr, but
> it's allowed with decltype, for example the following is valid code:
> > decltype( static_cast<std::string*>(nullptr)->compare("Hello") )
> my_var;
> > ++my_var;
> > I think decltype should be given another privilege:
>
> Random note: the code above has not been idiomatic C++ for at least the
> last 11 years; you're supposed to use std::declval.
>

Furthermore, this isn't a "special privilege" of decltype; it is a special
privilege of *unexecuted code in general*.
    std::string *p = nullptr;
    if (false) {
        std::cout << *p; // Line X
    }
Line X dereferences a null pointer, which is nominally undefined behavior;
however, *because that line is never actually executed by the program*,
it's actually totally fine.
The same applies not just to code that's skipped at runtime by `if
(any-false-condition)` but also to code that's *discarded* by `if
constexpr` and to code that's *unevaluated* because of `sizeof`, `alignof`,
`decltype`, `typeid`, etc. Unevaluated code is allowed to do anything it
wants at runtime, because it does nothing at runtime. :)

> just allow decltype to
> > get the type of anything irrespective of accessibility.
>

This would break the classic method of implementing
"expression-equivalence": the "You must write it three times" idiom.
https://godbolt.org/z/h5EYY8f7s

    template<class T>
    auto extract_foo(T t)
      noexcept(noexcept(t.foo))
      -> decltype( t.foo)
      { return t.foo; }

    int extract_foo(...) { return 42; }

    struct A { public: int foo = 7; };
    struct B { private: int foo = 8; };
    int a = extract_foo(A()); // a==7
    int b = extract_foo(B()); // b==42

If `decltype(t.foo)` was uniquely allowed to ignore access control, while
`noexcept` and `return` were not, this would break the idiom horribly.
In C++20 and later, an *almost* replacement (which also respects access
control) is

    template<class T>
    auto extract_foo(T t)
      noexcept(noexcept( t.foo))
      -> decltype(auto)
      requires requires { t.foo; }
      { return t.foo; }

but this newer version has a few corner cases where it's just a tad more
"eagerly instantiated" than the classic way: This version doesn't know its
own return type until its whole body has been instantiated, whereas the
classic version cleanly separates interface/return-type from
implementation/body and can thus figure out its own return type *without*
needing to instantiate its body quite yet.

HTH,
Arthur

Received on 2022-03-26 15:19:37