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.
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