Endofunctor概念或界面

时间:2016-11-28 00:39:03

标签: c++ functor generic-programming c++17 c++-concepts

从这里开始,(endo)仿函数能够获取一个对象并将其转换为另一个相同类型的对象。最简单的仿函数示例是身份:

struct Identity {
    template <typename T>
    T Apply(T x) {
        return x
    }
};

我需要一个标识通用Functor的“Functor类型”。我想做的是写代码:

class Sum {
  public:
    Sum(Functor* f, Functor* g) :
        f_(f),
        g_(g) {}
    template <typename T>
    T Apply(T x) { return f_->Apply(x) + g_->Apply(x); }
  private
    Functor* f_;
    Functor* g_;
};

我想到的第一个想法当然是使用虚拟类:

struct Functor {
    template <typename T>
    virtual T Apply(T x) = 0;
};

这种方法无法解决的问题是模板不能是虚拟的。

然后我尝试使用C ++概念。但是,如Specifying a concept for a type that has a member function template using Concepts LiteC++ Concepts: Can I define a concept that is itself a template?中所述 How to achieve "virtual template function" in C++不可能有一个“模板概念”。

最后我偶然发现了{{3}},因此我想出了以下可能的实现:

struct Functor {
    template <typename T>
    T Apply(const T& x);  // No more virtual!!!
};

// ... Identity and Sum declarations properly inheriting from Functor ...

template <typename T>
T Functor::Apply(T x) {
    if (Identity* specialized =
            dynamic_cast<Identity*>(this)) {
        return specialized->Apply(x);
    } else if (const Sum* specialized =
            dynamic_cast<const Sum*>(this)) {
        return specialized->Apply(x);
    } else ...
}

即使这是编译,但它不是最好的解决方案。主要问题是:性能和代码重复。 性能问题来自于每次在Functor上调用Apply时,必须解析Functor :: Apply中的long if子句。这是一个很大的问题,因为Functor可以深度嵌套(因此调用Apply可能会导致多次调用Functor :: Apply)。 “代码重复”问题非常明显,因为每次我想定义一个新的Functor时我都要修改Functor :: Apply添加一个新的if子句。

我在这里要问的是,是否有一种正确的(更干净的)方法来定义一个Functor接口/概念,可以创建像Sum这样的类。 接受C ++概念和重模板元编程。

P.S。所有代码片段都是有目的的尽可能简单。避免建议使用类而不是struct或添加const标识符或使用唯一指针,这不是这个问题的重点。

1 个答案:

答案 0 :(得分:2)

不幸的是,我能想到的大多数(最佳)解决方案要求您采用一些相当复杂的方法。当然,这不一定是坏事,但随着您设计程序的推进,它可能会使事情变得混乱。出于这个原因,我可能会提出一些更直接的建议:

template <typename F, typename G>
class Sum {
  public:          
    Sum(F& f, G& g) :
        f_(f),
        g_(g) {}
    template <typename T>
    inline T Apply(T x) { return f_.Apply(x) + g_.Apply(x); }
  private:
    F& f_;
    G& g_;
};

/*
    For every class like the above, you may want to define an 
    easy-to-use generating function to simplify instantiations: 
*/
template <typename F, typename G>
inline Sum<F, G> MakeSum(F& f, G& g)
{
    return Sum<F, G>(f, g);
}

#include <cmath>

struct SquareRoot {
    template <typename T>
    inline T Apply(T x)
    {
        return std::sqrt(x);
    }
};

struct Triple {
    template <typename T>
    inline T Apply(T x)
    {
        return T(3) * x;
    }
};

//    Example:

#include <iostream>

int main(void)
{
    using namespace std;
    SquareRoot square_root;
    Triple triple; 
    // For g++, don't forget to compile with -std=c++1z     
    auto sum = MakeSum(square_root, triple);
    cout << sum.Apply(1024) << endl;
}

当然,它没有其他技术那么强大,但它可能是一个很好的起点。