像C ++功能那样的侵入式反射?

时间:2019-03-28 08:54:29

标签: c++

我想创建一个功能模板,该模板创建某个类的所有合法/有效实例的列表。类本身以某种方式被告知每个成员可以接受的价值。功能模板:

template <typename T>
std::list<T> PossibleInstantiations();

现在,如果SomeClass 以某种方式包含有关其所有成员的法律实例的信息(在下面的示例中,i的法律实例为1,4,5,法律实例j中的分别是1.0、4.5),然后

PossibleInstantiations<SomeClass>(); 

应产生一个包含元素{SomeClass(1,1.0), SomeClass(1,4.5), SomeClass(4,1.0), SomeClass(4,4.5), SomeClass(5,1.0), SomeClass(5,4.5)}的列表。

当然,添加额外的元素(+关联的有效值)应该由PossibleInstantiations自动处理。

Someclass的实现方式如下。应当以哪种方式将管道添加到客户端类(例如MyClass),以及应如何实现PossibleInstantiations

class SomeClass
{
public:
    int i;
    static std::list<int> ValidValuesFori();

    double j;
    static std::list<double> ValidValuesForj();

    SomeClass(int i, double j);

    //int k;
    //static std::list<int> ValidValuesFork();  //could be implemented at some later stage. 

    //Which would mean the constructor becomes:
    //SomeClass(int i, int j, int k)

    //...
    //Extra wiring for pointing out that i and ValidValuesFori belong to each other,
    //and perhaps for pointing out that i is the first element in the constructor, or so?
    //..
};
static std::list<int> SomeClass::ValidValuesFori()
{
    return std::list<int>{1, 4, 5};
    //Other options:
    //std::list<int> ValidValues;
    //for (int i = 0; i < 1000; i++)
    //{
    //    if (i % 3 == 0)
    //      ValidValues.push_back(i);       
    //}
    //return ValidValues;
}
static std::list<double> SomeClass::ValidValuesForj()
{
    return std::list<double>{1.0, 4.5};
}
SomeClass::SomeClass(int i, double j)//or other constructor
    :i{ i }, j{ j } {}  

2 个答案:

答案 0 :(得分:3)

如果您可以轻松完成,为什么要加倍努力呢?您已经说过SomeClass应该知道其成员可以使用哪些值。您可以通过将GetPossibleImplementations()移至该类来明确这一点:

class SomeClass
{
public:
    static std::vector<SomeClass> GetPossibleImplementations()
    {
        std::vector<SomeClass> values;
        for (int i : ValidValuesFori())
            for (double j : ValidValuesForj())
                values.push_back(SomeClass(i, j));
        return values;
    }
    // other methods
    // [...]
}

然后,如果需要,您仍然可以添加模板功能:

template <typename T>
std::vector<T> GetPossibleImplementations()
{
    return T::GetPossibleImplementations();
}

将逻辑移到类中具有以下优点:

  • 只有该类知道哪些构造函数args有效,因此在其中具有逻辑是有意义的。
  • 有效参数值的笛卡尔积可能不足以适用于所有情况。如果i的某个值与j的某个值发生冲突该怎么办?
  • 您仍然可以将一些逻辑移到辅助函数中,例如笛卡尔积。

在某些情况下,您无法更改SomeClass的实现,并且需要外部解决方案。即使在那种情况下,我认为您也应该保留特定于逻辑的类:如上所述声明通用函数GetPossibleImplementations<T>(),但仅对特定的类实现:

template <typename T>
std::vector<T> GetPossibleImplementations();

template <>
std::vector<SomeClass> GetPossibleImplementations<SomeClass>()
{
    std::vector<SomeClass> values;
    for (int i : SomeClass::ValidValuesFori())
        for (double j : SomeClass::ValidValuesForj())
            values.push_back(SomeClass(i, j));
    return values;
}

两个版本之间的主要区别在于,如果模板参数T不支持T::GetPossibleImplementations(),则第一个版本会产生编译错误;如果GetPossibleImplementations<T>,则第二个版本会产生链接错误未实现。

答案 1 :(得分:1)

使用Cartesian product,我们可以这样做:

// cartesian_product_imp(f, v...) means
// "do `f` for each element of cartesian product of v..."
template<typename F>
void cartesian_product_imp(F f) {
    f();
}
template<typename F, typename H, typename... Ts>
void cartesian_product_imp(F f, std::vector<H> const& h,
                           std::vector<Ts> const&... vs) {
    for (H const& he: h) {
        cartesian_product_imp([&](Ts const&... ts){
                                  f(he, ts...);
                              }, vs...);
    }
}

template <typename... Ts>
std::vector<std::tuple<Ts...>> cartesian_product(std::vector<Ts> const&... vs) {
    std::vector<std::tuple<Ts...>> res;

    cartesian_product_imp([&](Ts const&... ts){
                              res.emplace_back(ts...);
                          }, vs...);
    return res;
}

template <typename T>
std::vector<T> PossibleInstantiations()
{
    auto validValuesByArgs = T::ValidValuesForArgs();
    auto validArgs =
        std::apply([](const auto&... args){ return cartesian_product(args...); },
                   validValuesByArgs);
    std::vector<T> res;

    std::transform(validArgs.begin(), validArgs.end(),
                   std::back_inserter(res),
                   [](const auto& t){
                       return std::apply([](const auto&... args) { return T(args...); },
                                         t);
                    });
    return res;
}

使用

class SomeClass
{
public:
    int i;
    double j;
    const std::string s;

    static std::tuple<std::vector<int>, std::vector<double>, std::vector<std::string>>
    ValidValuesForArgs() { return {{1, 4, 5}, {1.1, 4.5}, {"foo", "bar"}}; }

    SomeClass(int i, double j, const std::string& s) : i(i), j(j), s(s) {}
};

Demo