Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ variadic template template argument that matches any kind of parameters

I was wondering if it's possible to write a template function that can take any other arbitrary template as a parameter and properly match the template name (i.e. not just the resulting class). What I know to work is this:

template<template<typename ...> class TemplateT, typename... TemplateP> void f(const TemplateT<TemplateP...>& param); 

Which will match for instance for f(std::vector<int>()) or f(std::list<int>()) but will not work for f(std::array<int, 3>()), as the second parameter is a size_t and no type.

Now I guess one could do something crazy like:

template<template<typename ...> class TemplateT, size... Sizes, typename... TemplateP> void f(const TemplateT<Sizes..., TemplateP...>& param); 

Hoping that the compiler would properly derive either the TemplateP ellipsis or the Sizes ellipsis to be empty. But not only is it ugly, it also will still just work for templates that take either types or size_t parameters. It still won't match arbitrary templates for instance with bool parameters.

Same goes for an approach with overloading:

template<template<typename ...> class TemplateT, typename... TemplateP> void f(const TemplateT<TemplateP...>& param);  template<template<typename ...> class TemplateT, size... Sizes> void f(const TemplateT<Sizes...>& param); 

Furthermore, such approach wont' work if we would like to mix size_t and typenames. So what would be required to match anything would be something like this, where there are no constraints at all to what is allowed in the ellipsis:

template<template<...> class TemplateT, ... Anything> void f(const TemplateT<Anything...>& param); 

That syntax doesn't work but maybe there's other syntax to define something like this?

This is mainly me wondering what is possible in the language, thought there might actually be a use for it, if you have different templates where the first parameter is always fixed and you would like to change it based on the return type and keep everything else. Something like this:

template<     template<typename ValueT, ...> class TemplateT,     ... Anything,     typename ValueT,     typename ResultT = decltype(some_operation_on_value_t(std::declval<ValueT>())> TemplateT<ResultT, Anything...> f(const TemplateT<ValueT, Anything...>& in); 

So, any way to make this work in a completely generic way using pattern matching?

This is not purely a thought experiment, as the use case for this where I was stuck was to create pure functional primitives that operate on containers and will implicitly construct immutable result containers. If the result container has a different data type we need to know the type the container operates on, so the only requirement on any container would be that the first parameter of the template needs to be the input type so it can be replaced with a different output type in the result, but the code should be oblivious to any template argument coming after that and should not care whether it's a type or a value.

like image 563
Janick Bernet Avatar asked Sep 17 '14 14:09

Janick Bernet


People also ask

What is a template argument c++?

In C++ this can be achieved using template parameters. A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.

Can we pass Nontype parameters to templates?

Template non-type arguments in C++It is also possible to use non-type arguments (basic/derived data types) i.e., in addition to the type argument T, it can also use other arguments such as strings, function names, constant expressions, and built-in data types.

How do you expand a parameter pack?

Parameter packs can only be expanded in a strictly-defined list of contexts, and operator , is not one of them. In other words, it's not possible to use pack expansion to generate an expression consisting of a series of subexpressions delimited by operator , .


1 Answers

Your interesting construct has two levels with variadic templates.

  • An outer variadic template parameter list TemplateP & Sizes for a function template
  • An inner parameter pack as the template parameters of your template template parameter TemplateT, a class template

First, let's look at the inner TemplateT class: why can the ellipsis operator not not match something like TemplateT< int, 2 >? Well, the standard defines variadic templates in §14.5.3 as

template<class ... Types> struct Tuple { }; template<T ...Values> struct Tuple2 { }; 

where the template argument pack in the first case may only match types and in the second version only values of type T. In particular,

Tuple < 0 >    error;  // error, 0 is not a type! Tuple < T, 0 > error2; // T is a type, but zero is not! Tuple2< T >    error3; // error, T is not a value Tuple2< T, 0 > error4; // error, T is not a value 

are all malformed. Furthermore, it is not possible to fall back to something like

template<class ... Types, size_t ...Sizes> struct Tuple { }; 

because the standard states in §14.1.11:

If a template-parameter of a primary class template or alias template is a template parameter pack, it shall be the last template-parameter. A template parameter pack of a function template shall not be followed by another template parameter unless that template parameter can be deduced from the parameter-type-list of the function template or has a default argument (14.8.2).

In other words, for class templates only one variadic parameter pack may appear in the definition. Therefore the above (double)-variadic class definition is malformed. Because the inner class always needs such a combination, it is impossible to write something as general as you conceived.


What can be rescued? For the outer function template, some shards can be put together, but you won't like it. As long as the second parameter pack can be deduced from the first, two parameter packs may appear (in a function template). Therefore, a function such as

template < typename... Args, size_t... N > void g(const std::array< Args, N > &...arr); g(std::array< double, 3 >(), std::array< int, 5>()); 

is allowed, because the integer values can be deduced. Of course, this would have to be specialized for every container type and is far from what you had imagined.

like image 85
user1978011 Avatar answered Sep 30 '22 01:09

user1978011