【问题标题】:Generalized chaining of non-member functions in C++C++ 中非成员函数的广义链接
【发布时间】:2011-09-24 12:05:21
【问题描述】:

我不知道这是否可以实现,但鉴于这些功能\类:

float plus1(float x) { return x+1; }
float div2(float x) { return x/2.0f; }
template <typename T>
class chain {
public:
    chain(const T& val = T()) : val_(val) {}
    chain& operator<<( std::function<float (float)> func ) {
    val_ = func(val_);
    return *this;
  }
  operator T() const {
    return val_;
  }
  T val_;
};

我可以像这样链接在浮点数上运行的函数:

float x = chain<float>(3.0f) << div2 << plus1 << div2 << plus1;

但是,我想将其概括\扩展为能够在类型之间进行转换并具有带参数的函数。不幸的是,我不够聪明,无法弄清楚如何或是否可以做到这一点。 太具体了,我希望能够像这样做一些事情operator&lt;&lt; 只是一个随意的选择,最好我什至不必在开头写“链”); 另外,这些只是虚拟的例子,我不打算将它用于算术。

std::string str = chain<float>(3.0) << mul(2.0f) << sqrt << to_string << to_upper;

vec3d v = chain<vec3i>(vec3i(1,1,1)) << normalize << to_vec3<double>;

有什么想法吗?

【问题讨论】:

  • 它的目的是什么? std::string str = to_upper(to_string(sqrt(3.0 * 2.0));
  • 我看不出有任何理由这样做 - 看起来很糟糕。但无论如何,如果... template&lt;typename Q&gt; chain&amp; operator&lt;&lt;( std::function&lt;T(Q)&gt; func){...} 如果 Q 不能隐式转换为 T 它不会编译。编辑:你需要另一个重载来只拿一个 T 所以你也可以做你的 mul(2.0f)
  • 如果你真的想这样做,我会说使用 Haskell,但这可能是你获得灵感的地方。如果这能以简洁的方式完成,我会感到惊讶。
  • UncleBens,Dave:目的和我的观点是,从左到右比从括号内到外读更易读。
  • @stefaanv;实际上,灵感来自于与成员函数链接的传统方法返回对自身的引用。没用过haskell(不过如果像这样工作的话听起来很棒=)

标签: c++ templates metaprogramming method-chaining


【解决方案1】:

我想我明白你为什么要这么做了。它类似于 iostream 操纵器。

您总是需要从chain(...) 开始(即您永远无法神奇地执行int x = 1 &lt;&lt; plus(2) &lt;&lt; times(2) 之类的操作),但您可以重载operator intoperator float、...以允许隐式转换。

您还需要返回并定义每种类型(如 mul),然后实现采用 mul 或 const mul 的 operator&lt;&lt;,但总的来说它是可行的(但 PITA)

【讨论】:

    【解决方案2】:

    使用 boost::proto 的通用且可扩展的解决方案:

    #include <iostream>
    #include <boost/proto/proto.hpp>
    
    namespace bp = boost::proto;
    
    // -----------------------------------------------------------------------------
    // perform is a callable transform that take a function_ terminal and execute it
    // -----------------------------------------------------------------------------
    struct perform : bp::callable
    {
      template<class Sig> struct result;
      template<class This, class Func, class In>
      struct result<This(Func,In)> 
           : boost::result_of<typename boost::remove_reference<Func>::type(In)> {};
    
      template<class Func, class In>
      typename result<perform(Func &,In)>::type
      operator()( Func& f, In& in ) const
      {
        return f(in);
      }
    };
    
    // -----------------------------------------------------------------------------
    // Grammar for chaining pipe of functions
    // -----------------------------------------------------------------------------
    struct pipeline_grammar
    : bp::or_<
        bp::when<
            bp::bitwise_or<pipeline_grammar,pipeline_grammar>
              , pipeline_grammar(
                    bp::_right
                  , pipeline_grammar(bp::_left,bp::_state)
                    )
            >
          , bp::when<
                bp::terminal<bp::_>
              , perform(bp::_value, bp::_state) 
        >
    > {};
    
    // -----------------------------------------------------------------------------
    // Forward declaration of the pipeline domain
    // -----------------------------------------------------------------------------
    struct pipeline_domain;
    
    // -----------------------------------------------------------------------------
    // A pipeline is the top level DS entity
    // -----------------------------------------------------------------------------
    template<class Expr>
    struct  pipeline : bp::extends<Expr,pipeline<Expr>, pipeline_domain>
    {
      typedef bp::extends<Expr, pipeline<Expr>, pipeline_domain> base_type;
      pipeline(Expr const &expr = Expr()) : base_type(expr) {}
    
      // ---------------------------------------------------------------------------
      // A pipeline is an unary callable object
      // ---------------------------------------------------------------------------
      template<class Input>
      typename boost::result_of<pipeline_grammar(pipeline,Input)>::type
      operator()(Input const& in) const
      {
        pipeline_grammar evaluator;
        return evaluator(*this,in);
      }
    };
    
    // -----------------------------------------------------------------------------
    // the pipeline_domain make pipeline expression macthes pipeline_grammar
    // -----------------------------------------------------------------------------
    struct pipeline_domain 
         : bp::domain<bp::generator<pipeline>,pipeline_grammar>
    {};
    
    // -----------------------------------------------------------------------------
    // Takes a PFO instance and make it a pipeline terminal
    // -----------------------------------------------------------------------------
    template<class Func>
    typename bp::result_of::
    make_expr<bp::tag::terminal, pipeline_domain,Func>::type
    task( Func const& f )
    {
      return bp::make_expr<bp::tag::terminal,pipeline_domain>( f );
    }
    
    //--------------------------- Examples --------------------
    
    struct return_value
    {  
      template<class Sig> struct result;
      template<class This, class T>
      struct result<This(T)> : bp::detail::uncvref<T>
      {};
    
      return_value(int i = 1) : factor(i) {}
    
      template<class T> 
      T operator()(T const& in) const
      {
        return in*factor;
      }
    
      int factor;
    };
    
    struct say_hi
    {
      typedef void result_type;
    
      template<class T> 
      void operator()(T const& in) const
      {
        std::cout << "Hi from value = " << in << "\n";
      }
    };
    
    int main()
    {
      return_value r1,r2(5);
      (task(r1) | task(r2) | task(say_hi())) (7); // SHould print 35
    
      float k = 10,r;
      r = (task(r2) | task(r2) | task(r2) | task(r2))(k);
      std::cout << r << "\n"; // Should print 6250
    }
    

    基本思想是将函数对象包装成proto终端,构建一个小的|基于语法,让 proto 系统处理作文。

    【讨论】:

    • 哦,非常感谢,我有时间再研究一下。
    【解决方案3】:

    为了在类型之间进行转换,您希望所有内容都返回一个代理对象,该对象可以转换为任何类型。也许是基于 boost::variant 的东西。

    您还可以将运算符

    template <class UnaryFunction>
    chain& operator<<(UnaryFunction func) { _val = func(_val); return *this;}
    

    这将允许您使用任何类型的函数对象作为参数。

    要使用具有多个参数的函数,您可以使用绑定函数。这是 C++11 之前的增强功能,但现在它已成为标准,并且应该可以在任何 C++11 兼容的编译器上使用。

    【讨论】:

      【解决方案4】:

      这是我的 C++17 解决方案。

      #include <type_traits>
      #include <utility>
      
      template <class F>
      struct waterfall
      {
          waterfall(F&& f)
          : fn(std::forward<F>(f))
          {}
      
          template <class... Args>
          decltype(auto) operator()(Args&&... args) const {
              return fn(std::forward<Args>(args)...);
          }
      
          template <class T>
          auto then(T&& t) const & {
              return then_impl(fn, std::forward<T>(t));
          }
      
          template <class T>
          auto then(T&& t) const && {
              return then_impl(std::move(fn), std::forward<T>(t));
          }
      
      private:
          F fn;
      
          template <class In, class Out>
          static auto then_impl(In&& in, Out&& out)
          {
              auto fn = [in = std::forward<In>(in), out = std::forward<Out>(out)](auto&&... args)
              {
                  using InRet = std::invoke_result_t<In, decltype(args)...>;
      
                  if constexpr (std::is_invocable_v<Out, InRet>) {
                      return out(in(std::forward<decltype(args)>(args)...));
                  }
                  else {
                      in(std::forward<decltype(args)>(args)...);
                      return out();
                  }
              };
      
              return waterfall<decltype(fn)>(std::move(fn));
          }
      };
      

      并像这样使用它

      int main()
      {
          // Create a chain
          waterfall chain([](const char* s) {
              return 42;
          })
          .then([](auto x) {
              // x = 42 here
              return x + 1;
          })
          .then([] {
              // Ignoring value from previous function.
              // Send double to next one.
              return 3.14;
          })
          .then([](double value) {
              // etc...
              return true;
          });
      
          // chain signature is now bool(const char*)
      
          // Now call our functions in chain
          bool ret = chain("test");
      }
      

      【讨论】:

        猜你喜欢
        • 2011-03-22
        • 2021-02-15
        • 2014-07-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-12-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多