Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

overloading over function types with templates

Tags:

c++

There is a common abstraction for both containers and functions. I learned it in Haskell, and I'm trying to implement it in C++.

Most C++ programmers are familiar with std::transform, roughly speaking given a function from type A to B, you can convert a container of type A to a container of type B.

You can transform functions in a similar way, given a function foo from A to B, you can convert a function bar taking Z to A to a function foo . bar taking Z to B. The implementation is simple, it's just composition.

I wanted to define a function fmap, on containers and functions, to reflect this abstraction for generic programming.

The container was easy (I know this isn't fully general)

template <typename A, typename Func>
auto fmap(Func f, vector<A> in) {
  vector<decltype(f(in[0]))> out_terms{};
  for(auto vec : in)
    out_terms.push_back(f(vec));
  return out_terms;
}

However, the analogous function for functions makes me much more nervous.

template <typename FuncT, typename Func>
auto fmap(FuncT f, Func in) {
  return [f, in](auto x){
    return f(in(x));
  };
}

Although the template won't specialize for anything except callable things, I'm worried this will confuse overload resolution. I would like to introduce type constraints on the template parameters to restrict their resolution to function types to keep the name space clean. And I was going to ask how to do that.

This abstraction is extremely general, there are corresponding fmaps for pointers to values, which I suspect might conflict as well.

So I think my question is, can I have two different template implementations with the same template level signature? I'm almost certain the answer is no but maybe something similar can be faked. And if not, what tools are available today to distinguish between the overloads? Especially for function types.

This seems, to me, to be a textbook case for concepts, though I'm not sure.

Edit: Boost would be acceptable to use, and SFINAE in particular. I'm trying to find a solution that would be familiar to most programmers, and as convenient, and canonical as possible. I could rename fmap to compose, but then the programmer would have to know to pass compose to a template function accepting fmap. That would be unfortunate, because fmap is semantically unique.

Edit 2: A trivial example of how this is used.

template <typename T>
auto double_everything(T in){
  auto doublef = [](auto x){return 2*x;};
  return fmap(doublef, in);
}

It generalizes maps over containers to maps over "container like" things. So double_everything(vector<int> {1, 2, 3}) returns a vector with its elements doubled. But double_everything([](int x){ return x + 1; }) returns a function whose outputs are twice the outputs of the increment function. Which is like doubling a kind of list. The abstraction has some nice properties, I'm not just making it up. At any rate, renaming the function fmap to compose doesn't answer the question.

Edit 3: fmap for a template C takes functions from A to B to functions from C<A> to C<B> and satisfies fmap( compose(f, g) , c ) = fmap( f, fmap( g, c )). This is a nice structure preserving property.

Functions which do this for ranges already exist by different names. But ranges aren't the only templates on types. Here is fmap for std::optional:

template<typename T, typename Func>
auto fmap(Func f, optional<T> o) -> optional<f(*o)>{
  if(o)
    return f(*o);
  else
    {};
}

This implementation doesn't involve any range concepts at all, like thefmap for functions presented earlier. But it satisfies the semantic requirements for fmap.

I'm trying to define fmap for different overloads in the same way I would define a new operator * for a custom matrix type. So I would happily define fmap in terms of boost::transform_iterator. Then these algorithms would work with a function generic in terms of fmap.

Here is an example of such a function:

template < 
  template<typename, typename> class Cont, 
  typename Fmappable, 
  typename Alloc, 
  typename Func>
auto map_one_deep(Func f, Cont<Fmappable, Alloc> c){
  auto g = [f](Fmappable x){ return fmap(f, x); };
  return fmap(g, c);
}

now if we write

auto lists = vector<vector<int> > { {1, 2, 3}, {4, 5, 6} };
auto lists_squared = map_one_deep( [](int x){return x*x;} , lists);

lists_squared printed gives

1 4 9
16 25 36

If we instead had a vector of optionals, the optionals would be squared provided they contained elements.

I'm trying to understand how one should work with higher order functions in c++.

like image 587
Polymer Avatar asked Oct 19 '16 15:10

Polymer


People also ask

Can we have overloading of function templates?

You may overload a function template either by a non-template function or by another function template. The function call f(1, 2) could match the argument types of both the template function and the non-template function.

What is the difference between function overloading and templates?

What is the difference between function overloading and templates? Both function overloading and templates are examples of polymorphism features of OOP. Function overloading is used when multiple functions do quite similar (not identical) operations, templates are used when multiple functions do identical operations.

What are the different types of function overloading?

There are mainly two types of overloading, i.e. function overloading and operator overloading. Function overloading improves the code readability, thus keeping the same name for the same action.

What is the advantage of using template function over function overloading?

@kushal Yes but with template, the compiler create a new function for every type used, while with function overloading you keep control of how many function exist in the final program. Template makes the program bigger.


1 Answers

You can fake it with SFINAE, but you shouldn't. It's a matter of style and idiom.

Haskell is all about type classes, with a programmer expecting to have to spangle each type with all the clubs it belongs to. C++, in contrast, wants to be more implicit in specifying a type's capabilities. You've shown "vector" and "arbitrary callable" there, but why just vector? Why not an arbitrary container type? And this arbitrary container type I just wrote has an operator(), because reasons. So which one should it choose?

Bottom line, while you can use SFINAE tricks to resolve technical ambiguities, you shouldn't use them to resolve essential ambiguities. Just use two different names.

like image 174
Sneftel Avatar answered Oct 17 '22 03:10

Sneftel