45

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.

Janick Bernet
  • 20,544
  • 2
  • 29
  • 55
  • 9
    There's no way to even match *one* non-type template parameter if you don't know its type. I'm afraid you are asking for too many too good things. – n. m. could be an AI Sep 17 '14 at 15:13
  • 10
    @n.m. Or put another way, value-arguments are second-class template-arguments. (Use a wrapper (`std::integral_constant`) to avoid the disadvantages. (They should have implemented auto-wrapping there...) – Deduplicator Sep 17 '14 at 16:13
  • 1
    @n.m.: Agreed, it's already not possible for 'normal' parameters. On the other hand, variadic parameters were added to be able to specify more generic templates so there's some hope that something like this was added / will be added. – Janick Bernet Sep 17 '14 at 16:18
  • 2
    Possibly you may be interested in the answers to [this question](http://stackoverflow.com/q/23871757/3043539). – Constructor Sep 17 '14 at 18:37
  • 2
    I think the problems you would solve with this can usually be solved by using duck typing (e.g. "if it acts like a container" + `auto`/`decltype(*begin(param))`, instead of checking the parameters of the parameter directly) or subclassing (making a new derived class to change the template parameters). – leewz Mar 02 '15 at 03:23
  • Given what others have said, I agree that you may be trying to broad a focus for your question. If you modify it to say you want a container with n-ary types can you give me an example where you need n-ary sizes or n-ary bools or other literals for the template argument. I would guess for the most part you will only deal with one of those. But say for the sake of argument you do have n-ary sizes. I think you have it when you suggest overloading your function. – TimeHorse Mar 04 '15 at 20:39
  • Consider also specialization is for classes and overloading is for functions. You may actually be thinking of a class constructor (as suggested above) which has a predicate operator. This allows you to do partial template specialization. If you agree with this approach I can write it up as an answer for you. – TimeHorse Mar 04 '15 at 20:40

3 Answers3

4

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.

user1978011
  • 3,419
  • 25
  • 38
  • Hmm. I just read the section, too and according to that section int... or something like that isn't even allowed at all, I'm pretty sure however clang at least supports that. – Janick Bernet Apr 28 '15 at 15:29
  • Thanks for pointing this out. I looked further into this and I think the answer is there now. – user1978011 Apr 28 '15 at 20:44
1

You must have a metafunction that rebinds the type of container. Because you cannot just replace first template parameter:

vector<int, allocator<int> > input;
vector<double, allocator<int> > just_replaced;
vector<double, allocator<double> > properly_rebound;

So, just write such a metafunction for known set of containers.

template<class Container, class NewValue> class rebinder;

// example for vectors with standard allocator
template<class V, class T> class rebinder< std::vector<V>, T > {
public:
  typedef std::vector<T> type;
};
// example for lists with arbitrary allocator
template<class V, class A, class T> class rebinder< std::list<V,A>, T > {
  typedef typename A::template rebind<T>::other AT; // rebind the allocator
public:
  typedef std::list<T,AT> type; // rebind the list
};
// example for arrays
template<class V, size_t N> class rebinder< std::array<V,N>, T > {
public:
  typedef std::array<T,N> type;
};

Rules of rebinding may vary for different containers.

Also you might require a metafunction that extracts value type from arbitrary container, not only std-conformant (typedef *unspecified* value_type)

template<class Container> class get_value_type {
public:
  typedef typename Container::value_type type; // common implementation
};
template<class X> class get_value_type< YourTrickyContainer<X> > {
  ......
public:
  typedef YZ type;
};
Nickolay Merkin
  • 2,673
  • 1
  • 16
  • 14
  • Yes, that would work for that specific case of STl containers. But Qt containers for instance don't have allocators. Also I was looking for something more generic anyway, containers were just an example. – Janick Bernet Apr 28 '15 at 15:30
0

It would be awesome if we had such thing, as it would allow us to write a is_same_template trait in a breeze.
Until then, we specialize all the way.

Community
  • 1
  • 1
bit2shift
  • 656
  • 1
  • 9
  • 17