13

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

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • What are you trying to accomplish with this? – Sam Varshavchik May 19 '16 at 01:51
  • 1
    @SamVarshavchik, the reason for the question seems pretty clear from the question itself... – SergeyA May 19 '16 at 02:02
  • 3
    Given that you're the one asking this question, I have zero confidence in my answer. But throwing it out there anyway. Is the `long` overload in Niebler's proposal just to handle cv-`void`? – Barry May 19 '16 at 02:34
  • 1
    @Barry, Unless there's another type that doesn't take well to having a reference applied to it, yes. – chris May 19 '16 at 03:17
  • Where can I find information on how Eric's proposal works? – Claas Bontus May 19 '16 at 11:36
  • 2
    @ClaasBontus, It's similar to a common SFINAE technique that uses `decltype` in the return type to figure out whether an expression is valid. Here, there's no need to check an expression, only that `&&` can be applied to the type. Note that this was further simplified to remove `_Up` and use `_Tp&&` as the return type. If `_Tp&&` is a valid type, both overloads will take part in overload resolution and `int` wins over `long`. If it's not a valid type, only the second overload will be there. For more information, your best bet might be to look for expression SFINAE examples. – chris May 19 '16 at 11:55

1 Answers1

7

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

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Hmm, I found a new one that doesn't trigger the static assert: `using t = decltype(static_cast(std::declval));`. I might ask a separate question w/r/t whether there's anything in the standard that says this cast is not required to work. – T.C. May 20 '16 at 02:32
  • 1
    @T.C. I would've expected them to have equivalent validity so I am no help to you there. But I'm not even sure I'm right on this one. – Barry May 20 '16 at 02:54