C++ Templates - The Complete Guide: Understanding footnote comment about decltype and return type
Asked Answered
R

1

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.

Rubella answered 27/9, 2021 at 17:22 Comment(0)
P
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

Plutonic answered 27/9, 2021 at 17:30 Comment(9)
... because the comma may be overloaded.Adelladella
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?Rubella
@Rubella 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.Plutonic
@Plutonic in the case no worries and stay tuned, I'll ask another questionRubella
@Rubella cppreference says "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.Plutonic
@Rubella Ok, I think I worked it out from here, 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 :)Plutonic
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<T>().begin())) to have the same behavior, right?Rubella
@Rubella Yes, that appears to be correct godbolt.org/z/x4jMeoj63Plutonic
@cigien, I've asked a new question nonetheless, because there's still something unclear for me.Rubella

© 2022 - 2024 — McMap. All rights reserved.