Reputation: 6638
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
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
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
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 {};
}
Upvotes: 3