Home > OS >  Can C template template parameters accept templates that take non-type parameters?
Can C template template parameters accept templates that take non-type parameters?

Time:11-03

I have a function like this to implement fmap for C :

// Given a mapping F from T to U and a container of T, return a container of U
// whose elements are created by the mapping from the original container's
// elements.
template <typename F, template <typename...> typename Container, typename T>
Container<std::invoke_result_t<F&, const T&>> Fmap(F&& f,
                                                   const Container<T>& input);

The idea is to use a template template parameter (Container) to allow accepting any STL-like container. All of the ones in the actual STL I've tried work fine, but a custom container in our codebase doesn't work because it accepts a non-type template parameter

template <typename Key, int Foo = 256>
class MyContainer;

This causes a substitution failure from clang:

template template argument has different template parameters than its corresponding template template parameter

Is there a way to abstract over all template parameters, not just types? If not, is there a better way to structure my code to allow doing what I want without specializing for MyContainer and all others like it in particular?

CodePudding user response:

A template template parameter can only match one kind of template; that kind is determined by the template parameter list. You have to write another version of Fmap if you want to accept MyContainer. However, if you do, you can match any template that has one type parameter followed by any number of non-type parameters: it could be an int like in your example, or it could be a char and a bool...

template <typename F, template <typename, auto...> typename Container, typename T, auto ...Vs>
Container<std::invoke_result_t<F&, const T&>, Vs...> Fmap(F&& f, const Container<T, Vs...>& input) {
    return {};
}

Demo

CodePudding user response:

Since your function does not know the kind of template parameters of the template template parameter, and cannot use them, they all must have defaults in the actual template argument. You can exploit this fact by creating and using an alias template:

template <typename F, template <typename /* no pack */> typename Container, typename T>
Container<std::invoke_result_t<F&, const T&>> Fmap(F&& f,
                                                   const Container<T>& input);

template <typename X> using MyContainerDefault =  MyContainer<X>;

something = Fmap(someFunction, MyContainerDefault);

Having said that, there is a good reason why standard library algorithms do not accept or return containers. There are many resources that explain this, just search for why stl algorithms do not work with containers.

Another point worth mentioning is that in Haskell each "container" (functor) implements fmap in its own way, so one general implementation of Fmap is rather dubious. If you want to clone fmap, it should have an overload for each container or be a container's member function. Once you accept this, you no longer need the template template parameter because each container knows its own template parameters.

  • Related