The following code triggers a static assertion on libstdc++:
#include <utility>
using t = decltype(std::declval<const void>);
Should it?
Motivation for this question:
The following declval
implementation proposed by Eric Niebler (which is apparently a compile time optimization)
template<typename _Tp, typename _Up = _Tp&&>
_Up __declval(int);
template<typename _Tp>
_Tp __declval(long);
template<typename _Tp>
auto declval() noexcept -> decltype(__declval<_Tp>(0));
would be questionable if a user could legally observe the type of std::declval<const void>
. The signature in the standard
template <class T>
add_rvalue_reference_t<T> declval() noexcept;
results in the type const void ()
(or const void () noexcept
in C++17), whereas the proposed version results in the type void ()
(or void () noexcept
).
[declval] stipulates that:
If this function is odr-used (3.2), the program is ill-formed.
That's basically it. Where functions are concerned, odr-use means, from [basic.def.odr]:
A function whose name appears as a potentially-evaluated expression is odr-used if it is the unique lookup result or the selected member of a set of overloaded functions (3.4, 13.3, 13.4), unless it is a pure virtual function and either its name is not explicitly qualified or the expression forms a pointer to member (5.3.1).
But also:
An expression is potentially evaluated unless it is an unevaluated operand (Clause 5) or a subexpression thereof.
And [dcl.type.simple]:
The operand of the
decltype
specifier is an unevaluated operand (Clause 5).
So in decltype(std::declval<const void>)
, std::declval
isn't potentially evaluated and hence it's not odr-used. Since that's the one criteria on declval
for the program to be ill-formed and we don't meet it, I think libstdc++ is wrong to emit the static assertion.
Though I don't think this is a libstc++ thing. I think it's more of a question of when static_assert
s get triggered. The libstdc++ implementation of declval
is:
template<typename _Tp>
struct __declval_protector
{
static const bool __stop = false;
static typename add_rvalue_reference<_Tp>::type __delegate();
};
template<typename _Tp>
inline typename add_rvalue_reference<_Tp>::type
declval() noexcept
{
static_assert(__declval_protector<_Tp>::__stop,
"declval() must not be used!");
return __declval_protector<_Tp>::__delegate();
}
Both gcc and clang trigger that static_assert
in this context (but obviously not with decltype(std::declval<const void>())
, even though we're in an unevaluated context in both cases. I suspect that's a bug, but it may simply be underspecified in the standard what the correct behavior is with regards to triggering static_assert
s.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With