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

template <typename T>
struct IsIterable<
        T,
        decltype(
            std::begin(std::declval<std::add_lvalue_reference_t<T>>()),
            std::end(std::declval<std::add_lvalue_reference_t<T>>()),
            void()
        )
> : std::true_type {};

I thought I understood this.

The first IsIterable is a class template with a default template argument for the second parameter. It obviously is applicable for every type.

The second part is a partial template specialization of IsIterable. Thanks to SFINAE, it is only chosen when T has the member functions begin() and end().

Here is my first question I just came up with actually: the base template has a default argument, so this means it still has two template parameters. The partial specialization only has one template parameter. So does this mean that "one template parameter" always gets choosen before "two template parameters, one default"?

Also, do I understand it correctly that the first "base" template inherits from false_type, and the partial template specialization "adds" another inheritance level? (so does the partial specializations inheritance hierarchy look like this: false_type > true_type > IsIterable, where the definitions of false_type are hidden by true_type?)

Now onto my actual question. Why does the decltype expression have to evaluate to void? I thought that it wouldn't matter and I could write

template <typename T>
struct IsIterable<
        T,
        decltype(
            std::begin(std::declval<std::add_lvalue_reference_t<T>>()),
            std::end(std::declval<std::add_lvalue_reference_t<T>>()),
            bool() // **** change here ****
        )
> : std::true_type {};

as well. But this makes it so that the value of IsIterable is always false! Why is, when I change the partial specialization from void to bool, always the first template chosen?

Timo Türschmann
  • 4,388
  • 1
  • 22
  • 30
  • 3
    This is basically http://stackoverflow.com/questions/27687389/how-does-void-t-work. – T.C. Jan 10 '15 at 14:16
  • 1
    `partial template specialization "adds" another inheritance level?` No - when matched, the specialization is used instead of the primary template, not in addition to. The specialization may look wildly different from the primary, they don't have to have anything in common. – Igor Tandetnik Jan 10 '15 at 14:18
  • @T.C. do you know where to find a correct implementation of this? – Timo Türschmann Jan 10 '15 at 15:04

2 Answers2

4

So does this mean that "one template parameter" always gets choosen before "two template parameters, one default"?

No. A partial specialization is used when an instantiation matches its template argument list, and when it is "more specialized" than any other partial specializations that match (which is not relevant here, because there are no other partial specializations declared).

When you instantiate the template as IsIterable<Foo> the default template argument is used, so the instantiation is IsIterable<Foo, void>. If Foo has begin() and end() members then IsIterable<Foo, void> matches the partial specialization, which is more specialized than the primary template. When Foo doesn't have begin() and end() members the partial specialization is not usable, because the expression decltype(std::declval<T>().begin(), std::declval<T>().end(), void()) causes a subsitution failure when Foo is substituted in place of T.

Also, do I understand it correctly that the first "base" template inherits from false_type, and the partial template specialization "adds" another inheritance level?

No. There is no implicit inheritance relationship between a primary template and a specialization. (If you stop calling it a "base" template and call it by its proper name, the primary template, maybe you won't think there is any kind of "base" class relationship.)

When a partial specialization (or an explicit specialization) is used it is used instead of the primary template, not in addition to it.

Why does the decltype expression have to evaluate to void?

Because that's the type of the default template argument on the primary template. You are not supposed to provide an argument for the second template parameter, it's supposed to use the default, and that is void.

Why is, when I change the partial specialization from void to bool, always the first template chosen?

Because when you write IsIterable<Foo> that uses the default template argument, so is equivalent to IsIterable<Foo, void> which can never match a partial specialization of the form IsIterable<T, bool> because void is not the same type as bool!

If you made the partial specialization use bool() instead then you would have to write IsIterable<Foo, bool> for the partial specialization to match. You could do that ... but it's not how the trait is designed to be used. Alternatively you could change the default template argument on the primary template to be bool as well, but void is the idiomatic choice, since the specific type doesn't matter, all that matters is the default matches the specialization. You're meant to provide only one template argument and let the default be used for the second. And the partial specialization can only match if its second template argument is the same as the default template argument.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
3

The second part is a partial template specialization of IsIterable. Thanks to SFINAE, it is only chosen when T has the member functions begin() and end().

That's not all there is. The expression t.begin(), t.end(), void() for a t of type T must also have a valid type and that must be void for the specialization to be selected. (That's what the decltype achieves.)

Here is my first question I just came up with actually: the base template has a default argument, so this means it still has two template parameters. The partial specialization only has one template parameter. So does this mean that “one template parameter” always gets choosen before “two template parameters, one default”?

Every valid instantiation of the specialization is also a valid instantiation of the base case (it's an implication) so the specialization (if viable) is a better match.

Also, do I understand it correctly that the first “base” template inherits from false_type, and the partial template specialization “adds” another inheritance level? (so does the partial specializations inheritance hierarchy look like this: false_type > true_type > IsIterable, where the definitions of false_type are hidden by true_type?)

No, they are completely unrelated types. A specialization of a class template does not inherit implicitly from the general case.

If you want to see this live, try adding a large non-static data member (eg int[100]) to the general case and then compare the sizeof the instantiated types. If the special case is smaller, it cannot possibly be derived from the general case.

Now onto my actual question. Why does the decltype expression have to evaluate to void?

It doesn't have to but in order to make this work, you'll also have to change the default for the base case from void to bool or whatever. Be aware, however, that an overloaded operator, could cause you strange surprises if you pick anything but void.

The technique was nicely explained in a talk Walter Brown gave at CppCon 2014. If you have two hours sparse, I strongly recommend you watch the recording of his talk:

5gon12eder
  • 24,280
  • 5
  • 45
  • 92