jacobsa
jacobsa

Reputation: 6638

Can C++ template template parameters accept templates that take non-type parameters?

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?

Upvotes: 6

Views: 1038

Answers (3)

Enlico
Enlico

Reputation: 28510

To add up to the general suggestion that was already given (overload for each container), I think you'd get quite a good overview of one way that thing can be accomplished by exploring how Louis Dionne's Boost.Hana defines Functor and other concepts (yeah, there are also Applicative, Monad, Comonad and others), and how types like boost::hana::basic_tuple or boost::hana::optional (a compile-time optional) implement it. The interesting part to read with respect to your question is probably tag dispatching. Here is a complete example of how to make std::array a hana::Functor, so that you can apply hana::transform on it.

Another interesting reading that could broaden your view on this topic, is P1895, which is also linked off a post by Barry Revzin on the topic (not exactly in favour of the tag_invoke approach that the linked proposal is about).

Some time ago, I also asked a question about this topic, but I haven't received a satisfying answer yet.

Upvotes: 0

n. m. could be an AI
n. m. could be an AI

Reputation: 120089

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.

Upvotes: 0

Nelfeal
Nelfeal

Reputation: 13269

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

Upvotes: 3

Related Questions