Polymorphic (general) functions as arguments in C ++

I am developing a relatively simple program (a calculator actually). Nevertheless, I decided to make all the components of my program as universal as possible, because:

  • This is a good practice.
  • It makes things interesting.

As part of this program, I use the Tuple class that I am writing. I know that the class already exists, but I like to have full control over my code, and this is just an exercise.

One thing I need to do is convert the tuple of expressions (where the expressions themselves) into a tuple containing the result of evaluating the expressions. In short, with me (with trivial parts omitted):

 template <class T> class Expression { public: virtual T Eval() = 0; // ... }; template <class First, class ... Rest> class Tuple { // ... private: First first; Tuple<Rest ...> rest; }; 

And I would like to specialize in a tuple of this type:

 template <template <class> class R, class First, class ... Rest> class Tuple<R<First>, R<Rest> ...> { // and here is the problem: Tuple<First, Rest ...> Transform(function<template<class T> T(R<T>)>); }; 

After that, I could do this:

 template <class T> // There has to be a better way to do this T Eval(Expression<T>& expr){ return expr.Eval(); } // ... Tuple<First, Rest ...> tuple = exprs.Transform(Eval); 

There are several places where I’m not sure how to go about things, and a real expert will be appreciated who could help me here. I expect this code to not compile due to minor flaws, but that is not the point. My main concern is the line that I marked. If I remember correctly from a brief period, I studied Haskell, this function should be of rank-2 (if not to comment, and I will remove the tag). It just doesn't look right. Is there any way to do this?

Update:

I was advised to try passing a functor with a common operator () as an argument to the template, but that didn't work either.

+6
source share
2 answers

I think you can do this quite simply without C ++ 14. I am going to assume a few things about how your Tuple is created, namely that these two ctors exist:

 Tuple(First, Rest... ); // (1) Tuple(First, const Tuple<Rest...>& ); // (2) 

We need one type trait: with the help of the function with which we are transformed, we need to know what types it produces:

 template <typename T, typename F> using apply_t = decltype(std::declval<F>()(std::declval<T>())); 

(God I love C ++ 11)

With this, we can easily determine the return type and simply call the function recursively:

 template <typename First, typename... Rest> struct Tuple { template <typename F> Tuple<apply_t<First, F>, apply_t<Rest, F>...> Transform(F func) { return {func(first), rest.Transform(func)}; // hence the need // for ctor (2) }; }; 

(Depending on how you wrote your Tuple , you may or may not need a base case for a trivial conversion that just returns a Tuple<> or a base case that just returns Tuple<apply_t<First, F>> . Anyway , not a big deal).

And you don’t even need to specialize Tuple at all. You just need to go to the desired functors. For instance:

 struct Zero { template <typename T> int operator()(T ) { return 0; } }; struct Incr { template <typename T> T operator()(T x) { return x + 1; } }; Tuple<int, double, char> tup(1, 2.0, 'c'); auto z = tup.Transform(Zero{}); // z is Tuple<int, int, int>{0, 0, 0} auto i = tup.Transform(Incr{}); // i is Tuple<int, double, char>{2, 3.0, 'd'} 

Here's a complete code example that registers all types too. Of course, with C ++ 14 we can do these inline:

 auto i2 = tup.Transfom([](auto x) -> decltype(x) {return x+1; }); // i2 is a Tuple<int, double, char>{2, 3.0, 'd'}; // without the trailing decltype, it gets deduced as Tuple<int, double, int>. 
+2
source

The usual trick in C ++ 14 is to use some index_sequence (see here ), and then something like:

 template<typename ... Args, size_t ... I> auto evaluate(Tuple<Args ...> const& t, index_sequence<I...>) { return make_tuple(evaluate(get<I>(t))...); } 

See, for example, this answer for an example of this approach (the difference is that a function call is additionally called here).

So what you need in your Tuple class for this:

  • The implementation of the user-defined get function, which behaves similarly to std::get , i.e. accepts the arguments of the variational index.
  • An implementation of the user-defined function make_tuple , which behaves similarly to std::make_tuple and builds a tuple from a comma-separated list.

In addition, you need a evaluate function template that can evaluate a single expression, but I think you already have this.


EDIT: I just realized that the above may not be very useful for you. Rather, it should be noted that you can do this also recursively:

 template<typename ... Args> auto evaluate(Tuple<Args ...> const& t) { return tuple_cat(make_tuple(evaluate(t.first)), evaluate(t.rest)); } template<typename T> auto evaluate(Tuple<T> const& t) { return evaluate(t.first); } 

Again, you need the make_tuple function, the make_tuple tuple tuple_cat and the evaluator with a single evaluate .

+3
source

Source: https://habr.com/ru/post/978628/


All Articles