C++ Templates – The Complete Guide: Understanding footnote comment about decltype and return type

The 2nd edition of C++ Templates – The Complete Guide features the following code at page 435

#include <string>
#include <type_traits>

template<typename T, typename = void>
struct HasBeginT : std::false_type {};

template<typename T>
struct HasBeginT<T, std::void_t<decltype(std::declval<T>().begin())>>
    : std::true_type {};

and comments that decltype(std::declval<T>().begin()) is used to test whether it is valid to call .begin() on a T.

This all makes sense, I think…

What staggers me is the comment in the footnote:

Except that decltype(call-expression) does not require a nonreference, non-void return type to be complete, unlike call expressions in other contexts. Using decltype(std::declval<T>().begin(), 0) instead does add the requirement that the return type of the call is complete, because the returned value is no longer the result of the decltype operand.

I don’t really understand it.

In an attempt to play with it, I tried to see what it behaves with a void member begin, with the following code

struct A {
    void begin() const;

struct B {

static_assert(HasBeginT<A>::value, "");
static_assert(!HasBeginT<B>::value, "");

but both assertions pass with or without the , 0.


Your demo uses void begin() const; to test the following

… instead does add the requirement that the return type of the call is complete

But a void return type is not the same as an incomplete return type. For that you could try

struct X;

struct A {
    X begin() const;

Here, X is indeed incomplete, and now the , 0 matters. With it, the first static_assert won’t pass, but it passes without the , 0.