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 {};
}
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.