Suppose you want to write a template function that accepts any specialization of std::vector
. Your first try would probably be something like this:
template<typename Value> void accept_any_vector(std::vector<Value> v);
However, this does not actually accept any vector specialization. There is a second template argument for the allocator, which has a default value that nearly everyone uses. But if somebody has a vector with a custom allocator, then your function won’t match.
Okay, so add an allocator, too.
template<typename Value, typename Allocator> void accept_any_vector(std::vector<Value, Allocator> v);
This works today, but it may not work tomorrow.
A future version of std::vector
might add new template arguments, provided that they have suitable defaults that preserve existing behavior.¹ We don’t want that future version to break us, so we should slurp up all the template arguments that exist:
template<typename...Args> void accept_any_vector(std::vector<Args...> v);
Now, once we’ve accepted any kind of vector, we have lost the template parameters that name the value type and allocator. You might think you could rescue them by naming them and putting all the future nonsense in the variadic portion:
template<typename Value, typename Allocator, typename...Extra> void accept_any_vector(std::vector<Value, Allocator, Extra...> v);
However, this doesn’t work:
// msvc error C2977: 'std::vector': too many template arguments // clang error: too many template arguments for class template 'vector' // icc error: too many arguments for class template "std::vector" // gcc (no errors)
Fortunately, you can recover the Value
and Allocator
from std::vector<Args...>
because std::vector
lets you access the underlying type and allocator through member types.
template<typename...Args> void accept_any_vector(std::vector<Args...> v) { using vector = std::vector<Args...>; using Value = typename vector::value_type; using Allocator = typename vector::allocator_type; ... }
C++ standard library types generally provide these member types to allow you to recover the template type arguments from the type. Other libraries are hit or miss.
¹ The standard also permits functions to accept default parameters or have additional default template arguments.² This is used primarily for SFINAE purposes, so that some overloads become removed from consideration if particular requirements are not met. The standard has the concept of “addressable functions”, which are the functions that the standard guarantees will never be overloaded or have a signature different from the one printed in the standard.
² We ran afoul of this issue in an April Fool’s article about invoke-oriented programming.
Even this is insufficient in the face of non-type template parameters: consider something like
. I don’t believe there is a way to generically handle arbitrary combinations of type and non-type template parameters.
This is one reason that we are pursuing Universal Template Parameters in P1985. Univeral template parameters match any template argument kind and with a pack of them you can match anything!
Why doesn’t the `Extra…` code work? I’ve written quite a lot of template / consteval code in the recent years, and I still don’t understand what’s the problem here.
Also, why is this error diagnosed during the first pass, before the template is even instantiated? That’s broken. It shouldn’t exist until instantiated, and compiler shouldn’t check it when it doesn’t exist.
I think you meant “this does not actually accept all vector specializations”.
Wouldn’t std::tuple_element_t<0, std::tuple<Args…>> also work to recover value_type? (and 1 for allocator_type)