12

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.

Enlico
  • 23,259
  • 6
  • 48
  • 102

1 Answers1

15

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.

demo

cigien
  • 57,834
  • 11
  • 73
  • 112
  • 3
    ... because the comma may be overloaded. – StoryTeller - Unslander Monica Sep 27 '21 at 17:39
  • Yeah, the answer truly tells how I misread/misunderstood the text. But know that I know how I should read it, I still don't understand how it works. Why does the comme have that effect? – Enlico Sep 27 '21 at 17:41
  • 1
    @Enlico Actually, I'm not sure why it has that effect. StoryTeller's comment seems to hint at that, but I'm not sure I get why. I'll see if I can work it out. You *could* edit the question to ask *why*, but that should probably be a different question. – cigien Sep 27 '21 at 17:58
  • @cigien in the case no worries and stay tuned, I'll ask another question – Enlico Sep 27 '21 at 18:05
  • @Enlico cppreference [says](https://en.cppreference.com/w/cpp/language/decltype) "The type need not be complete or have an available destructor, and can be abstract. **This rule doesn't apply to sub-expressions**: in `decltype(f(g()))`, `g()` must have a complete type, but `f()` need not." So it's not just comma, but any subexpression. I still don't know *why* the rule exists. – cigien Sep 27 '21 at 18:08
  • @Enlico Ok, I think I worked it out from [here](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3233.html), it's in the solution section. Basically, `decltype(f(g())` or `decltype(f(), 0)` for that matter, might need to do overload resolution, which requires conversions, and that requires type completeness. I get what StoryTeller meant now; as usual they're several steps ahead of me :) – cigien Sep 27 '21 at 18:19
  • Oh, so the `, 0` is "only" a mean to trigger overload resolution (_because the comma may be overloaded_) which, in turn, requires the type-completeness of the return type of `f()`. I could as well define `auto f = [](auto const&){};` and then do `decltype(f(std::declval().begin()))` to have the same behavior, right? – Enlico Sep 27 '21 at 19:13
  • @Enlico Yes, that appears to be correct https://godbolt.org/z/x4jMeoj63 – cigien Sep 27 '21 at 19:22
  • @cigien, I've asked a [new question](https://stackoverflow.com/questions/69352671/c-templates-the-complete-guide-wording-of-footnote-about-decltype-and-retur) nonetheless, because there's still something unclear for me. – Enlico Sep 27 '21 at 20:05