On Fri, Mar 25, 2022 at 2:27 PM Giuseppe D'Angelo via Std-Proposals <std-proposals@lists.isocpp.org> 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