Операторская композиция в с ++ - PullRequest
0 голосов
/ 05 октября 2018

Мне интересно, есть ли элегантное решение для составления математических операторов в C ++.Под оператором я имею в виду что-то вроде следующего:

template<class H>
class ApplyOp {
    H h;
public:
    ApplyOp(){}
    ApplyOp(H h_i) : h(h_i) {}

    template<class argtype>
    double operator()(argtype f,double x){
        return h(x)*f(x);
    }
};

В приведенном выше классе используется «вспомогательная функция» h(x).Например,

struct Helper{
    Helper(){}
    double operator()(double x){return x*x;}
};

struct F{
    F(){}
    double operator()(double x){return exp(x);}
};

int main()
{
    Helper h;
    F f;
    ApplyOp<Helper> A(h);

    std::cout<<"A(f,2.0) = "<<A(f,2.0)<<std::endl; //Returns 2^2*exp(2) = 29.5562...

    return 0;
}

Теперь я хотел бы составить оператор дважды или более раз, то есть вычислить A^2(f,2.0).В приведенном выше примере это вернет h(x)*h(x)*f(x).Обратите внимание, что это не композиция функции, т.е. я не хочу вычислять A(A(f,2.0),2.0).Скорее, подумайте с точки зрения вычислительной мощности матрицы: если h(x) = M (матрица), я хочу M*M*...*M*x.

Я смог использовать std::bind() для достижения желаемого результата для A^2 (но не более высоких степеней!) Следующим образом:

auto g = std::bind(&ApplyOp<Helper>::operator()<F>,&A,f,std::placeholders::_1);

Получив g, яможно применить A^2(f,2.0), просто позвонив A(g,2.0).В приведенных выше примерах это вернуло бы h(x)*h(x)*f(x) = x*x*x*x*exp(x)

Как бы я обобщил это для итеративного применения оператора A N раз?Мне действительно понравился ответ, размещенный здесь , но он не совсем работает здесь.Я пытался делать вложенные std:bind с, но быстро попал в глубокие ошибки компилятора.

Есть идеи?

Полный рабочий пример:

#include<iostream>
#include<math.h>
#include<functional> //For std::bind

template<class H>
class ApplyOp {
    H h;
public:
    ApplyOp(){}
    ApplyOp(H h_i) : h(h_i) {}

    template<class argtype>
    double operator()(argtype f,double x){
        return h(x)*f(x);
    }
};

struct Helper{
    Helper(){}
    double operator()(double x){return x*x;}
};

struct F{
    F(){}
    double operator()(double x){return exp(x);}
};

int main()
{
    Helper h;
    F f;
    ApplyOp<Helper> A(h);

    std::cout<<"A(f,2.0) = "<<A(f,2.0)<<std::endl; //Returns 2^2*exp(2) = 29.5562...

    auto g = std::bind(&ApplyOp<Helper>::operator()<F>,&A,f,std::placeholders::_1);

    std::cout<<"A^2(f,2.0) = "<<A(g,2.0) <<std::endl; //Returns 2^4*exp(2) = 118.225... 

    return 0;
}

Ответы [ 4 ]

0 голосов
/ 05 октября 2018

Попробуйте, используя специализацию шаблона

#include<iostream>
#include<math.h>
#include<functional> //For std::bind

template<class H>
class ApplyOp {
    H h;
public:
    ApplyOp(){}
    ApplyOp(H h_i) : h(h_i) {}

    template<class argtype>
    double operator()(argtype f,double x){
        return h(x)*f(x);
    }
};

struct Helper{
    Helper(){}
    double operator()(double x){return x*x;}
};

struct F{
    F(){}
    double operator()(double x){return exp(x);}
};

// C++ doesn't permit recursive "partial specialization" in function
// So, make it a struct instead
template<typename T, typename U, typename W, int i>
struct Binder {
    auto binder(U b, W c) {
        // Recursively call it with subtracting i by one
        return [&](T x){ return b(Binder<T, U, W, i-1>().binder(b, c), x); };
    }
};

// Specialize this "struct", when i = 2
template<typename T, typename U, typename W>
struct Binder<T, U, W, 2> {
    auto binder(U b, W c) {
        return [&](T x){ return b(c, x); };
    }
};

// Helper function to call this struct (this is our goal, function template not
// struct)
template<int i, typename T, typename U, typename W>
auto binder(U b, W d) {
    return Binder<T, U, W, i>().binder(b, d);
}

int main()
{
    Helper h;
    F f;
    ApplyOp<Helper> A(h);

    std::cout<<"A(f,2.0) = "<<A(f,2.0)<<std::endl; //Returns 2^2*exp(2) = 29.5562...

    // We don't need to give all the template parameters, C++ will infer the rest
    auto g = binder<2, double>(A, f);

    std::cout<<"A^2(f,2.0) = "<<A(g,2.0) <<std::endl; //Returns 2^4*exp(2) = 118.225... 

    auto g1 = binder<3, double>(A, f);

    std::cout<<"A^3(f,2.0) = "<<A(g1,2.0) <<std::endl; //Returns 2^6*exp(2) = 472.2

    auto g2 = binder<4, double>(A, f);

    std::cout<<"A^4(f,2.0) = "<<A(g2,2.0) <<std::endl; //Returns 2^8*exp(2) = 1891.598... 

    return 0;
}
0 голосов
/ 05 октября 2018

Итак, вы хотите иметь возможность умножать функции.Ну, это звучит хорошо.Почему бы не + и - и /, пока мы там?

template<class F>
struct alg_fun;

template<class F>
alg_fun<F> make_alg_fun( F f );

template<class F>
struct alg_fun:F {
  alg_fun(F f):F(std::move(f)){}
  alg_fun(alg_fun const&)=default;
  alg_fun(alg_fun &&)=default;
  alg_fun& operator=(alg_fun const&)=default;
  alg_fun& operator=(alg_fun &&)=default;

  template<class G, class Op>
  friend auto bin_op( alg_fun<F> f, alg_fun<G> g, Op op ) {
    return make_alg_fun(
      [f=std::move(f), g=std::move(g), op=std::move(op)](auto&&...args){
        return op( f(decltype(args)(args)...), g(decltype(args)(args)...) );
      }
    );
  }

  template<class G>
  friend auto operator+( alg_fun<F> f, alg_fun<G> g ) {
    return bin_op( std::move(f), std::move(g), std::plus<>{} );
  }
  template<class G>
  friend auto operator-( alg_fun<F> f, alg_fun<G> g ) {
    return bin_op( std::move(f), std::move(g), std::minus<>{} );
  }
  template<class G>
  friend auto operator*( alg_fun<F> f, alg_fun<G> g ) {
    return bin_op( std::move(f), std::move(g),
      std::multiplies<>{} );
  }
  template<class G>
  friend auto operator/( alg_fun<F> f, alg_fun<G> g ) {
    return bin_op( std::move(f), std::move(g),
      std::divides<>{} );
  }

  template<class Rhs,
    std::enable_if_t< std::is_convertible<alg_fun<Rhs>, F>{}, bool> = true
  >
  alg_fun( alg_fun<Rhs> rhs ):
    F(std::move(rhs))
  {}

  // often doesn't compile:
  template<class G>
  alg_fun& operator-=( alg_fun<G> rhs )& {
    *this = std::move(*this)-std::move(rhs);
    return *this;
  }
  template<class G>
  alg_fun& operator+=( alg_fun<G> rhs )& {
    *this = std::move(*this)+std::move(rhs);
    return *this;
  }
  template<class G>
  alg_fun& operator*=( alg_fun<G> rhs )& {
    *this = std::move(*this)*std::move(rhs);
    return *this;
  }
  template<class G>
  alg_fun& operator/=( alg_fun<G> rhs )& {
    *this = std::move(*this)/std::move(rhs);
    return *this;
  }
};
template<class F>
alg_fun<F> make_alg_fun( F f ) { return {std::move(f)}; }

auto identity = make_alg_fun([](auto&& x){ return decltype(x)(x); });
template<class X>
auto always_return( X&& x ) {
  return make_alg_fun([x=std::forward<X>(x)](auto&&... /* ignored */) {
    return x;
  });
}

Я думаю, что по этому поводу.

auto square = identity*identity;

у нас также может быть тип-стерты alg funs.

template<class Out, class...In>
using alg_map = alg_fun< std::function<Out(In...)> >;

это те, которые поддерживают такие вещи, как *=.alg_fun обычно не набирайте erase достаточно, чтобы.

template<class Out, class... In>
alg_map<Out, In...> pow( alg_map<Out, In...> f, std::size_t n ) {
  if (n==0) return always_return(Out(1));
  auto r = f;
  for (std::size_t i = 1; i < n; ++i) {
    r *= f;
  }
  return r;
}

можно было бы сделать более эффективно.

Тестовый код:

auto add_3 = make_alg_fun( [](auto&& x){ return x+3; } );
std::cout << (square * add_3)(3)  << "\n";; // Prints 54, aka 3*3 * (3+3)

alg_map<int, int> f = identity;
std::cout << pow(f, 10)(2) << "\n"; // prints 1024

Liveпример .

Вот более эффективный режим, который работает без стирания типа:

inline auto raise(std::size_t n) {
  return make_alg_fun([n](auto&&x)
    -> std::decay_t<decltype(x)>
  {
    std::decay_t<decltype(x)> r = 1;
    auto tmp = decltype(x)(x);

    std::size_t bit = 0;
    auto mask = n;
    while(mask) {
      if ( mask & (1<<bit))
        r *= tmp;
      mask = mask & ~(1<<bit);
      tmp *= tmp;
      ++bit;
    }
    return r;
  });
}
template<class F>
auto pow( alg_fun<F> f, std::size_t n ) {
  return compose( raise(n), std::move(f) );
}

Живой пример .Он использует новую функцию compose в alg_fun:

  template<class G>
  friend auto compose( alg_fun lhs, alg_fun<G> rhs ) {
    return make_alg_fun( [lhs=std::move(lhs), rhs=std::move(rhs)](auto&&...args){
        return lhs(rhs(decltype(args)(args)...));
    });
  }

, которая выполняет compose(f,g)(x) := f(g(x))

Ваш код теперь буквально становится

alg_fun<Helper> h;
alg_fun<F> f;

auto result = pow( h, 10 )*f;

, что h(x)*h(x)*h(x)*h(x)*h(x)*h(x)*h(x)*h(x)*h(x)*h(x)*f(x).За исключением (с эффективной версией), я только один раз вызываю h и просто поднимаю результат до степени 10.

0 голосов
/ 05 октября 2018

Я имею в виду, что вы можете использовать другой класс:

template <typename T, std::size_t N>
struct Pow
{
    Pow(T t) : t(t) {}

    double operator()(double x) const
    {
        double res = 1.;

        for (int i = 0; i != N; ++i) {
            res *= t(x);
        }
        return res;
    }

    T t;  
};

И использовать

ApplyOp<Pow<Helper, 2>> B(h); вместо ApplyOp<Helper> A(h);

Демо

0 голосов
/ 05 октября 2018

Исходя из того, что я понял из вашего вопроса, вы, по сути, пытаетесь определить

A^1(h, f, x) = h(x) * f(x)
A^n(h, f, x) = h(x) * A^(n-1)(h, f, x)

Если вы открыты для использования C ++ 17, вот что вы можете построить на :

#include <iostream>
#include <math.h>

template <int N>
struct apply_n_helper {
  template <typename H, typename F>
  auto operator()(H h, F f, double x) const {
    if constexpr(N == 0) {
      return f(x);
    } else {
      return h(x) * apply_n_helper<N - 1>()(h, f, x);
    }
  }
};

template <int N>
constexpr auto apply_n = apply_n_helper<N>();

int main() {
  auto sqr = [](double x) { return x * x; };
  auto exp_ = [](double x) { return exp(x); };

  std::cout << apply_n<100>(sqr, exp_, 2.0) << '\n';
  std::cout << apply_n<200>(sqr, exp_, 2.0) << '\n';
  return 0;
}

Если C ++ 17 не вариант, вы можете легко переписать его, чтобы использовать специализации шаблона вместо constexpr-if.Я оставлю это как упражнение.Вот ссылка на проводник компилятора с этим кодом: https://godbolt.org/z/5ZMw-W

EDIT Оглядываясь назад на этот вопрос, я вижу, что вы, по сути, пытаетесь вычислить (h(x))^n * f(x) таким образом, чтобына самом деле вам не нужно делать какие-либо циклы во время выполнения, и сгенерированный код эквивалентен чему-то вроде:

auto y = h(x);
auto result = y * y * ... * y * f(x)
              \_____________/
                  n times
return result;

Другим способом достижения этого было бы иметь что-то следующее

#include <cmath>
#include <iostream>

template <size_t N, typename T>
T pow(const T& x) {
    if constexpr(N == 0) {
        return 1;
    } else if (N == 1) {
        return x;
    } else {
        return pow<N/2>(x) * pow<N - N/2>(x);
    }
}

template <int N>
struct apply_n_helper {
    template <typename H, typename F>
    auto operator()(H h, F f, double x) const {
        auto tmp = pow<N>(h(x));
        return tmp * f(x);
    }
};

template <int N>
constexpr auto apply_n = apply_n_helper<N>();

int main()
{
    auto sqr = [](double x) { return x * x; };
    auto exp_ = [](double x) { return exp(x); };

    std::cout << apply_n<100>(sqr, exp_, 2.0) << '\n';
    std::cout << apply_n<200>(sqr, exp_, 2.0) << '\n';
    return 0;
}

Здесь использование функции pow избавляет нас от оценки h(x) несколько раз.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...