Search code examples
c++fmt

Why is the `count()` function overload in `fmt/core.h` implemented like this?


In fmt/core.h, I noticed the function count_named_args() which uses the template function count with a given predicate.

And I found the overloaded version for count version is weird:

template <bool B1, bool B2, bool... Tail> constexpr auto count() -> size_t {
  return (B1 ? 1 : 0) + count<B2, Tail...>();
}

why do we need to take a template parameter bool B2 to extract the next boolean value explicitly, and not the parameter pack bool... Tail directly?

If I remove the bool B2 stuff, and try compile:

template <bool B1, bool... Tail> constexpr auto count() -> size_t {
  return (B1 ? 1 : 0) + count<Tail...>();
}
static_assert(count<false>() == 0);
static_assert(count<true, false>() == 1);

It gives an error as it is ambiguous to determine the overload when the parameter number reduces to the last one:

size_t count<false,>(void) noexcept
size_t count<false>(void) noexcept

Solution

  • Looking at the current main, there are two function templates:

    template <bool B = false> constexpr auto count() -> size_t { return B ? 1 : 0; }
    template <bool B1, bool B2, bool... Tail> constexpr auto count() -> size_t {
      return (B1 ? 1 : 0) + count<B2, Tail...>();
    }
    

    If those were instead

    template <bool B = false> constexpr auto count() -> size_t { return B ? 1 : 0; }
    template <bool B1, bool... Tail> constexpr auto count() -> size_t {
      return (B1 ? 1 : 0) + count<Tail...>();
    }
    

    then for call count<false>(), both candidates are considered and as per overload resolution rules neither is more specialized than the other -> ambiguity. Remember, parameter packs can be empty. Why is having an extra parameter pack not considered less specialized, that I honestly do not know.

    Only having <bool B1, bool... Tail> would not work for zero arguments and having extra non-template count for this case would not work for corner cases of count<pack...>() and pack being empty. On the other hand <bool B = false> can still be called like count() thanks to the default value.

    I think <bool B = false> and <bool B1, bool B2, bool... Tail> is the most generic and simplest solution.