Puzzle #8 (based on C++17)

#include <stdio.h>

template <typename Foo, typename ...Bars>
constexpr auto baz(Foo(*)(Bars......))
{
  return sizeof...(Bars);
}

int main()
{
  return baz(::printf);
}

With given code and C++17 compiler, pick one answer:

  • Guaranteed to return 0 from main.
  • Guaranteed to return 1 from main.
  • Guaranteed to return 2 from main.
  • Guaranteed to return 3 from main.
  • Guaranteed to return 4 from main.
  • Guaranteed to return something other from main.
  • Undefined behavior.
  • Implementation defined.
  • Will not compile.
Click here for the answer

The correct answer is: Guaranteed to return 1 from main.

First of all, here’s the paragraph that allows such declaration:

Where syntactically correct and where “…” is not part of an abstract-declarator, “, …” is synonymous with “…”.

So our ...... is synonymous for ..., .... We can write our baz like this:

template <typename Foo, typename ...Bars>
constexpr auto baz(Foo(*)(Bars..., ...))

Now, doesn’t the last ellipsis look familiar? That’s right! That’s a declaration of variadic arguments.

With knowledge what every character of the code means, let’s find out what baz actually do.

From its declaration we know that it takes a function pointer, and deduces return and argument types. We can write it like this:

template <typename ReturnType, typename ...Arguments>
constexpr auto baz(ReturnType(*)(Arguments..., ...))

Q: But why it there the variadic argument ellipsis? A: We explicitly specify it for compiler. Thanks to that, compiler will be able to match a pointer to function that has variadic arguments in its signature (e.g. std::printf).

It’s the same like we’d e.g. explicitly specify that first argument of passed function needs to be an int:

template <typename ReturnType, typename ...Arguments>
constexpr auto baz(ReturnType(*)(int, Arguments..., ...))

Ok, enough about the signature, now: the body.

  return sizeof...(Arguments);

What the Arguments will be? It’ll be a parameter pack, with all types explicitly specified in the signature of the function that has been passed to the baz. Whoah, sounds complex. But it isn’t. Consider this:

#include <stdio.h>
#include <string>
#include <iostream>

template <typename ReturnType, typename ...Arguments>
auto bar(ReturnType(*)(Arguments..., ...))
{
  return sizeof...(Arguments);
}

void foo(int, int, int, int, int, ...) {}

int main()
{
  std::cout << bar(::printf) << '\n';
  std::cout << bar(::fprintf) << '\n';
  std::cout << bar(::snprintf) << '\n';
  std::cout << bar(foo) << '\n';
}

(here are signatures of functions)

int printf( const char*, ... );
int fprintf( std::FILE*, const char*, ... );
int snprintf( char*, std::size_t, const char*, ... );

Output will be:

1
2
3
5

Now it’s pretty obvious, isn’t it?

So TL;DR baz() will return number of arguments explicitly specified in the signature of the function, that it takes.

Thanks for reading o/