5

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?

jacobsa
  • 5,719
  • 1
  • 28
  • 60
  • 1
    No, there is no way to take any template. – Nicol Bolas Nov 03 '22 at 01:11
  • You don't need to specialise, you only need an alias like `template using MyContainerDefault = MyContainer;` and use `MyContainerDefault` to pass to `Fmap`. Having said that, there is a reason why the standard library algorithms do not accept or return containers, and that reason is *good*. – n. m. could be an AI Nov 03 '22 at 04:21
  • @n.m. Thanks; do you think you could expand on both those points in an answer? – jacobsa Nov 03 '22 at 05:30

3 Answers3

3

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

Nelfeal
  • 12,593
  • 1
  • 20
  • 39
  • Thanks, the `auto` syntax completes part of the puzzle for me. My hope was that there is a syntax I can use to say "any template parameter, type or non-type". I guess that doesn't exist? I would hate to have to write separate overloads for one type parameter followed by non-type parameter, and two, and three, and... Not to mention the fact that they could be interleaved. – jacobsa Nov 03 '22 at 01:55
  • I'm not sure if a "type-or-non-type parameter" syntax could even exist, I assume it would cause some issues with matching. – Nelfeal Nov 03 '22 at 01:59
  • @jacobsa One thing you could try (but it's probably not worth the effort) is to change your container template by turning non-type parameters into type parameters. See [this answer](https://stackoverflow.com/a/54156647/3854570) ("second approach") for details. – Nelfeal Nov 03 '22 at 02:23
  • @jacobsa Sadly "any template parameter, type or non-type" is not on the menu. Maybe in C++29. – n. m. could be an AI Nov 03 '22 at 04:14
  • @Nelfeal, I guess it's not just "type-or-non-type", but "type-or-non-type-or-template". – Enlico Nov 03 '22 at 08:05
1

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 liked 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.

Enlico
  • 23,259
  • 6
  • 48
  • 102
0

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.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • I don't think I understand the purpose of `MyContainerDefault`. Are you saying that callers need to explicitly specify that? The users shouldn't need to know about it. Anyway I am coming around to the idea that there should be a separate specialization per container, like you said; perhaps I'm being too fancy. – jacobsa Nov 03 '22 at 06:50