Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it allowed to use decltype on std::declval<T> (the function itself, not the result of calling it)?

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

like image 384
T.C. Avatar asked May 19 '16 01:05

T.C.


1 Answers

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

like image 74
Barry Avatar answered Nov 02 '22 21:11

Barry