What are some uses of template template parameters?
I think you need to use template template syntax to pass a parameter whose type is a template dependent on another template like this:
template <template<class> class H, class S>
void f(const H<S> &value) {
}
Here, H
is a template, but I wanted this function to deal with all specializations of H
.
NOTE: I've been programming c++ for many years and have only needed this once. I find that it is a rarely needed feature (of course handy when you need it!).
I've been trying to think of good examples, and to be honest, most of the time this isn't necessary, but let's contrive an example. Let's pretend that std::vector
doesn't have a typedef value_type
.
So how would you write a function which can create variables of the right type for the vectors elements? This would work.
template <template<class, class> class V, class T, class A>
void f(V<T, A> &v) {
// This can be "typename V<T, A>::value_type",
// but we are pretending we don't have it
T temp = v.back();
v.pop_back();
// Do some work on temp
std::cout << temp << std::endl;
}
NOTE: std::vector
has two template parameters, type, and allocator, so we had to accept both of them. Fortunately, because of type deduction, we won't need to write out the exact type explicitly.
which you can use like this:
f<std::vector, int>(v); // v is of type std::vector<int> using any allocator
or better yet, we can just use:
f(v); // everything is deduced, f can deal with a vector of any type!
UPDATE: Even this contrived example, while illustrative, is no longer an amazing example due to c++11 introducing auto
. Now the same function can be written as:
template <class Cont>
void f(Cont &v) {
auto temp = v.back();
v.pop_back();
// Do some work on temp
std::cout << temp << std::endl;
}
which is how I'd prefer to write this type of code.
Get number of template parameters with template template function
There is one...
° Introduction
Like @Yakk pointed out in his comment to my other answer (without saying it explicitly), it is not possible to 'count' the number of parameters declared by a template. It is, on the other hand, possible to 'count' the number of arguments passed to an instantiated template.
Like my other answer shows it, it is rather easy to count these arguments.
So...
If one cannot count parameters...
How would it be possible to instantiate a template without knowing the number of arguments this template is suppose to receive ???
Note
If you wonder why the wordinstantiate(d)has been stricken throughout this post,
you'll find its explanation in the footnote. So keep reading... ;)
° Searching Process
- If one can manage somehow to try to
instantiatea template with an increasing number of arguments, and then, detect when it fails using SFINAE (Substitution Failure Is Not An Error), it should be possible to find a generic solution to this problem... Don't you think ? - Obviously, if one wants to be able to also manage non-type parameters, it's dead.
- But for templates having only
typename
parameters...
There is one...
Here are the elements with which one should be able to make it possible:
A template class declared with only
typename
parameters can receive any type as argument. Indeed, although there can have specializations defined for specific types,
a primary template cannot enforce the type of its arguments.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- The above statement might no longer be true from C++20 concepts.
I cannot try ATM, but @Yakk seems rather confident on the subject. After his comment:
I think concepts breaks this. "a primary template cannot enforce the type of its arguments." is false.
- He might be right if constraints are apply before the template instantiation. But...
- By doing a quick jump to the introduction to Constraints and concepts, one can read, after the first code example:
"Violations of constraints are detected at compile time, early in the template instantiation process, which leads to easy to follow error messages."
- To be confirmed...
- The above statement might no longer be true from C++20 concepts.
It is perfectly possible to create a template having for sole purpose to
be instantiated with any number of arguments. For our use case here, it might contain onlyint
s... (let's call itIPack
).It is possible to define a member template of
IPack
to define the NextIPack
by adding anint
to the arguments of the currentIPack
. So that one can progressively increase its number of arguments...Here is maybe the missing piece. It is maybe something that most people don't realize.
- (I mean, most of us uses it a lot with templates when, for example, the template accesses a member that one of its arguments must have, or when a trait tests for the existence of a specific overload, etc...)
But I think it might help in finding solutions sometimes to view it differently and say:
- It is possible to declare an arbitrary type, built by assembling other types, for which the evaluation by the compiler can be delayed until it is effectively used.
- Thus, it will be possible to inject the arguments of an
IPack
into another template...
Lastly, one should be able to detect if the operation succeeded with a testing trait making use of
decltype
andstd::declval
. (note: In the end, none of both have been used)
° Building Blocks
Step 1: IPack
template<typename...Ts>
struct IPack {
private:
template<typename U> struct Add1 {};
template<typename...Us> struct Add1<IPack<Us...>> { using Type = IPack<Us..., int>; };
public:
using Next = typename Add1<IPack<Ts...>>::Type;
static constexpr std::size_t Size = sizeof...(Ts);
};
using IPack0 = IPack<>;
using IPack1 = typename IPack0::Next;
using IPack2 = typename IPack1::Next;
using IPack3 = typename IPack2::Next;
constexpr std::size_t tp3Size = IPack3::Size; // 3
Now, one has a means to increase the number of arguments,
with a convenient way to retrieve the size of the IPack
.
Next, one needs something to build an arbitrary type
by injecting the arguments of the IPack
into another template.
Step 2: IPackInjector
An example on how the arguments of a template can be injected into another template.
It uses a template specialization to extract the arguments of anIPack
,
and then, inject them into theTarget
.
template<typename P, template <typename...> class Target>
struct IPackInjector { using Type = void; };
template<typename...Ts, template <typename...> class Target>
struct IPackInjector<IPack<Ts...>, Target> { using Type = Target<Ts...>; };
template<typename T, typename U>
struct Victim;
template<typename P, template <typename...> class Target>
using IPInj = IPackInjector<P, Target>;
//using V1 = typename IPInj<IPack1, Victim>::Type; // error: "Too few arguments"
using V2 = typename IPInj<IPack2, Victim>::Type; // Victim<int, int>
//using V3 = typename IPInj<IPack3, Victim>::Type; // error: "Too many arguments"
Now, one has a means to inject the arguments of an IPack
into a Victim
template, but, as one can see, evaluating Type
directly generates an error if the number of arguments does not
match the declaration of the Victim
template...
Note
Have you noticed that theVictim
template is not fully defined ?
It is not a complete type. It's only a forward declaration of a template.
- The template to be tested will not need to be a complete type
for this solution to work as expected... ;)
If one wants to be able to pass this arbitrary built type to some detection trait one will have to find a way to delay its evaluation.
It turns out that the 'trick' (if one could say) is rather simple.
It is related to dependant names. You know this annoying rule
that enforces you to add ::template
everytime you access a member template
of a template... In fact, this rule also enforces the compiler not to
evaluate an expression containing dependant names until it is
effectively used...
- Oh I see ! ...
So, one only needs to prepare theIPackInjector
s without
accessing itsType
member, and then, pass it to our test trait, right ?
It could be done using something like that:
using TPI1 = IPackInjector<IPack1, Victim>; // No error
using TPI2 = IPackInjector<IPack2, Victim>; // No error
using TPI3 = IPackInjector<IPack3, Victim>; // No error
Indeed, the above example does not generate errors, and it confirms
that there is a means to prepare the types to be built and evaluate
them at later time.
Unfortunately, it won't be possible to pass these pre-configured
type builders to our test trait because one wants to use SFINAE
to detect if the arbitrary type can be instantiated or not.
And this is, once again, related to dependent name...
The SFINAE rule can be exploited to make the compiler silently
select another template (or overload) only if the substitution
of a parameter in a template is a dependant name.
In clear: Only for a parameter of the current template instantiation.
Hence, for the detection to work properly without generating
errors, the arbitrary type used for the test will have to be
built within the test trait with, at least, one of its parameters.
The result of the test will be assigned to the Success
member...
Step 3: TypeTestor
template<typename T, template <typename...> class C>
struct TypeTestor {};
template<typename...Ts, template <typename...> class C>
struct TypeTestor<IPack<Ts...>, C>
{
private:
template<template <typename...> class D, typename V = D<Ts...>>
static constexpr bool Test(int) { return true; }
template<template <typename...> class D>
static constexpr bool Test(...) { return false; }
public:
static constexpr bool Success = Test<C>(42);
};
Now, and finally, one needs a machinery that will successively try
to instantiate our Victim
template with an increasing number of arguments. There are a few things to pay attention to:
- A template cannot be declared with no parameters, but it can:
- Have only a parameter pack, or,
- Have all its parameters defaulted.
- If the test procedure begins by a failure, it means that the template must take more arguments. So, the testing must continue until a success, and then, continue until the first failure.
- I first thought that it might make the iteration algorithm using template specializations a bit complicated... But after having thought a little about it, it turns out that the start conditions are not relevant.
- One only needs to detect when the last test was
true
and next test will befalse
.
- There must have a limit to the number of tests.
- Indeed, a template can have a parameter pack, and such a template can receive an undetermined number of arguments...
Step 4: TemplateArity
template<template <typename...> class C, std::size_t Limit = 32>
struct TemplateArity
{
private:
template<typename P> using TST = TypeTestor<P, C>;
template<std::size_t I, typename P, bool Last, bool Next>
struct CheckNext {
using PN = typename P::Next;
static constexpr std::size_t Count = CheckNext<I - 1, PN, TST<P>::Success, TST<PN>::Success>::Count;
};
template<typename P, bool Last, bool Next>
struct CheckNext<0, P, Last, Next> { static constexpr std::size_t Count = Limit; };
template<std::size_t I, typename P>
struct CheckNext<I, P, true, false> { static constexpr std::size_t Count = (P::Size - 1); };
public:
static constexpr std::size_t Max = Limit;
static constexpr std::size_t Value = CheckNext<Max, IPack<>, false, false>::Count;
};
template<typename T = int, typename U = short, typename V = long>
struct Defaulted;
template<typename T, typename...Ts>
struct ParamPack;
constexpr std::size_t size1 = TemplateArity<Victim>::Value; // 2
constexpr std::size_t size2 = TemplateArity<Defaulted>::Value; // 3
constexpr std::size_t size3 = TemplateArity<ParamPack>::Value; // 32 -> TemplateArity<ParamPack>::Max;
° Conclusion
In the end, the algorithm to solve the problem is not that much complicated...
After having found the 'tools' with which it would be possible to do it, it only was a matter, as very often, of putting the right pieces at the right places... :P
Enjoy !
° Important Footnote
Here is the reason why the word
intantiate(d)has been stricken at the places where it was used in relation to theVictim
template.
The word instantiate(d) is simply not the right word...
It would have been better to use try to declare, or to alias the type of a future instantiation of the Victim
template.
(which would have been extremely boring) :P
Indeed, none of the Victim
templates gets ever instantiated within the code of this solution...
As a proof, it should be enough to see that all tests, made in the code above, are made only on forward declarations of templates.
And if you're still in doubt...
using A = Victim<int>; // error: "Too few arguments"
using B = Victim<int, int>; // No errors
template struct Victim<int, int>;
// ^^^^^^^^^^^^^^^^
// Warning: "Explicit instantiation has no definition"
In the end, there's a full sentence of the introduction which might be stricken, because this solution seems to demonstrate that:
- It is possible to 'count' the number of parameters declared by a template...
- Without instantiation of this template.
Can concepts be used with template template parameters?
The shorter hand type-constraint syntax for concepts:
template <Concept T>
struct C { };
is only valid for those cases where Concept
's first template parameter is a type parameter. When that is not the case, you have to simply use the long form syntax: a requires-clause:
template <template <typename> class Z>
requires M<Z, Concrete_X>
struct C {};
The equivalent longer-form for my initial example is:
template <typename T> requires Concept<T>
struct C { };
The long form and short form mean the same thing - there's no functionality different here.
Cascade variadic template template parameters
You cannot have CascadeRight. T1 is not a typename, it is a template, and so are most of the others, but the last one is a typename. You cannot have different parameter kinds (both types and templates) in the same parameter pack. You also cannot have anything after a parameter pack.
You can have CascadeLeft like this:
template <typename K, template <typename...> class ... T>
class CascadeLeft;
template <typename K>
class CascadeLeft<K>
{
using type = K;
};
template <typename K,
template <typename...> class T0,
template <typename...> class... T>
class CascadeLeft<K, T0, T...>
{
using type = typename CascadeLeft<T0<K>, T...>::type;
};
Frankly, std::vector<std::vector<double>>
is much more transparent than CascadeLeft<double, std::vector, std::vector>
, so I wouldn't bother.
Exact rules for matching variadic template template parameters in partial template specialization
Since defect report resolution P0522R0 was adopted, exact matching of template parameter lists for template template parameter match is no longer needed, and correct output according to the standard is:
I am std::variant
I am other type
In the current draft (which also contains changes related to C++20 concepts) relevant standard excerpts are temp.arg.template#3-4 (bold emphasis mine):
- A template-argument matches a template template-parameter P when P is at least as specialized as the template-argument A. In this comparison, if P is unconstrained, the constraints on A are not considered. If P contains a template parameter pack, then A also matches P if each of A's template parameters matches the corresponding template parameter in the template-head of P. Two template parameters match if they are of the same kind (type, non-type, template), for non-type template-parameters, their types are equivalent ([temp.over.link]), and for template template-parameters, each of their corresponding template-parameters matches, recursively. When P's template-head contains a template parameter pack ([temp.variadic]), the template parameter pack will match zero or more template parameters or template parameter packs in the template-head of A with the same type and form as the template parameter pack in P (ignoring whether those template parameters are template parameter packs).
- A template template-parameter P is at least as specialized as a template template-argument A if, given the following rewrite to two function templates, the function template corresponding to P is at least as specialized as the function template corresponding to A according to the partial ordering rules for function templates. Given an invented class template X with the template-head of A (including default arguments and requires-clause, if any):
- (4.1) Each of the two function templates has the same template parameters and requires-clause (if any), respectively, as P or A.
- (4.2) Each function template has a single function parameter whose type is a specialization of X with template arguments corresponding to the template parameters from the respective function template where, for each template parameter PP in the template-head of the function template, a corresponding template argument AA is formed. If PP declares a template parameter pack, then AA is the pack expansion PP... ([temp.variadic]); otherwise, AA is the id-expression PP.
If the rewrite produces an invalid type, then P is not at least as specialized as A.
So, as we see, exact (up to special rules for parameter pack) parameter matching is now considered only in the case parameter list of template template parameter contains a pack (like your original example), otherwise only the new at least as specialized as relation is used to test matching, which defines for both template template parameter and argument respective function templates and tests whether parameter-induced function is at least as specialized as argument-induced function according to partial ordering rules for template functions.
In particular, matching A=std::variant
to P=template<typename> typename V
we get these corresponding function templates:
template<typename...> class X;
template<typename T> void f(X<T>); // for P
template<typename... Ts> void f(X<Ts...>); // for A
So, to prove A matches P, we need to prove f(X<T>)
is at least as specialized as f(X<Ts...>)
. temp.func.order#2-4 says:
- Partial ordering selects which of two function templates is more specialized than the other by transforming each template in turn (see next paragraph) and performing template argument deduction using the function type. The deduction process determines whether one of the templates is more specialized than the other. If so, the more specialized template is the one chosen by the partial ordering process. If both deductions succeed, the partial ordering selects the more constrained template (if one exists) as determined below.
- To produce the transformed template, for each type, non-type, or template template parameter (including template parameter packs thereof) synthesize a unique type, value, or class template respectively and substitute it for each occurrence of that parameter in the function type of the template. ...
- Using the transformed function template's function type, perform type deduction against the other template as described in [temp.deduct.partial].
Transformed template of f(X<T>)
is f(X<U1>)
and of f(X<Ts...>)
is f(X<U2FromPack>)
, where U1
and U2FromPack
are two synthesized unique types. Now, temp.deduct.partial#2-4,8,10 says:
- Two sets of types are used to determine the partial ordering. For each of the templates involved there is the original function type and the transformed function type.
[Note 1: The creation of the transformed type is described in [temp.func.order]. — end note]
The deduction process uses the transformed type as the argument template and the original type of the other template as the parameter template. This process is done twice for each type involved in the partial ordering comparison: once using the transformed template-1 as the argument template and template-2 as the parameter template and again using the transformed template-2 as the argument template and template-1 as the parameter template.- The types used to determine the ordering depend on the context in which the partial ordering is done:
- (3.1) In the context of a function call, the types used are those function parameter types for which the function call has arguments.130
- (3.2) In the context of a call to a conversion function, the return types of the conversion function templates are used.
- (3.3) In other contexts the function template's function type is used.
- Each type nominated above from the parameter template and the corresponding type from the argument template are used as the types of P and A.
- Using the resulting types P and A, the deduction is then done as described in [temp.deduct.type]. ... If deduction succeeds for a given type, the type from the argument template is considered to be at least as specialized as the type from the parameter template.
- Function template F is at least as specialized as function template G if, for each pair of types used to determine the ordering, the type from F is at least as specialized as the type from G. F is more specialized than G if F is at least as specialized as G and G is not at least as specialized as F.
Now, in our case, per 3.3, the function type itself is the only one considered among types to determine ordering. So, per 2, 8 and 10, to know whether f(X<T>)
is at least as specialized as f(X<Ts...>)
we need to see whether void(X<T>)
is at least as specialized as void(X<Ts...>)
, or, equivalently, whether deduction from type for P=void(X<Ts...>)
from A=void(X<U1>)
succeeds. It does according to temp.deduct.type#9-10:
- If P has a form that contains <T> or <i>, then each argument Pi
of the respective template argument list of P is compared with the corresponding argument Ai of the corresponding template argument list of A. If the template argument list of P contains a pack expansion that is not the last template argument, the entire template argument list is a non-deduced context. If Pi is a pack expansion, then the pattern of Pi is compared with each remaining argument in the template argument list of A. Each comparison deduces template arguments for subsequent positions in the template parameter packs expanded by Pi. ...- Similarly, if P has a form that contains (T), then each parameter type Pi of the respective parameter-type-list ([dcl.fct]) of P is compared with the corresponding parameter type Ai of the corresponding parameter-type-list of A. ...
Here, per 10, comparison of functions types results in a single comparison of X<Ts...>
and X<U1>
, which, according to 9, succeeds.
Thus, deduction is succesful, so f(X<T>)
is indeed at least as specialized as f(X<Ts...>)
, so std::variant
matches template<typename> typename V
. Intuitively, we gave 'more general' template template argument which should work nicely for intended usage of a more specific template template parameter.
In practice, different compilers enable P0522R0 changes under different circumstances, and cppreference template parameters page (section Template template arguments) contains links and information on GCC, Cland and MSVC. In particular, GCC in C++17+ mode enables it by default (and for previous standards with compiler flag fnew-ttp-matching
), but Clang doesn't in any mode unless -frelaxed-template-template-args
flag is provided, thus you got the difference in output for them. With the flag, Clang also produces correct behaviour (godbolt).
Passing template template parameters
Flip your CollectionHandler
declaration around to dissect the TypeCollection
through template specialization:
template <class TypeCollection>
class CollectionHandler;
template <class... Types>
class CollectionHandler<TypeCollection<Types...>> { };
Related Topics
Get Memory Overflow Caused by a Memory Leak and the Application Keep Running and Allocating
Why Is There Not an Std::Is_Struct Type Trait
Convert Windows Filetime to Second in Unix/Linux
Math-Like Chaining of the Comparison Operator - as In, "If ( (5<J<=1) )"
How to Read a Binary File into a Vector of Unsigned Chars
Is Passing a C++ Object into Its Own Constructor Legal
Array Initialization Use Const Variable in C++
Is It Smart to Replace Boost::Thread and Boost::Mutex with C++11 Equivalents
How Would You Implement Your Own Reader/Writer Lock in C++11
Difference Between Try-Catch Syntax for Function
Hide or Crop Overlapping Text in Qlabel
Building Glew on Windows with Mingw
Linking Fortran and C++ Binaries Using Gcc
Sort Array by First Item in Subarray C++
A Warning - Comparison Between Signed and Unsigned Integer Expressions
How to Include Header Files in Gcc Search Path