C++ puzzle #8
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.