设计迭代器适配器以在不相关的对象之间进行转换

时间:2018-11-14 00:54:55

标签: templates iterator c++14 template-meta-programming

我有以下MWE:

#include <iostream>
#include <vector>

using namespace std;

class LegacyWidgetData
{
  private:
    double _a;
    double _b;

  public:
    LegacyWidgetData()
      : _a(0), _b(0)
    {}

    LegacyWidgetData(const double &a, const double &b) 
      : _a(a), _b(b)
    {}

    LegacyWidgetData(const LegacyWidgetData& w)
      : _a(w.a()), _b(w.b())
    {}

    inline double &a()
    {
      return _a;
    }

    inline double a() const
    {
      return _a;
    }

    inline double &b()
    {
      return _b;
    }

    inline double b() const
    {
      return _b;
    }
};

template <std::size_t D>
class GenericWidgetData
{
private:
  double data[D];

public:
  GenericWidgetData(double a, double b)
  {
    data[0] = a;
    data[1] = b;
  }

  GenericWidgetData(double a, double b, double c)
  {
    data[0] = a;
    data[1] = b;
    data[2] = c;
  }

  double get(int idx)
  {
    return data[idx];
  }

  void set(int idx, const double& v)
  {
    data[idx] = v;
  }
};

template <typename Iterator>
void dummyFunction(Iterator begin, Iterator end)
{
  for (auto it = begin; it != end; it++)
  {
    cout << "Before: " << it->a() << "," << it->b() << "\t";
    it->a() += 1;
    it->b() -= 1;
    cout << "After: " << it->a() << "," << it->b() << "\n";
  }
}

int main()
{
  vector<LegacyWidgetData> c1{{1, 2}, {3, 4}, {5, 6}};
  dummyFunction(c1.begin(), c1.end());

  vector<GenericWidgetData<3>> c2{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};

  // dummyFunction(c2.begin(), c2.end());  // Will not compile
  return 0;
}

我有以下假设/限制:

  1. 我无法修改LegacyWidgetDatadummyFunction的实现
  2. 我可以将方法添加到GenericWidgetData,并根据需要添加任何迭代器适配器。

我想要的是某种迭代器适配器,当将其应用于任何类型的GenericWidgetData的迭代器时,它会提供类似于LegacyWidgetData的迭代器的迭代器,而无需任何缓存/创建所涉及的中间对象。如果可以使用模板元编程在编译时完成操作,那就大棒了!

2 个答案:

答案 0 :(得分:1)

您可以做的是在GenericWidgetData时向D == 2添加模板特化,然后实现.a().b()函数以匹配LegacyWidgetData的接口。

即使可以编写在编译时可以通过一些元编程技巧进行切换的迭代器适配器,也很难被其自己的作者理解。

答案 1 :(得分:-1)

如果具有迭代器适配器,则通常将需要两个这样的对象。

如果您有很多这样的算法,则有必要将向量隐藏在另一个类中,该类的begin / end方法将返回适配器。差不多是这样的:

templace <std::size_t D> class MyContainer
{
public:
    MyAdaptor<D> begin();
    MyAdaptor<D> end();
private:
    std::vector<GenericWidgetData<D>> data;
};

但是,将适当的功能简单地添加到GenericWidgetData可能是有意义的。在这种情况下,您可能需要一些专门知识,例如b()仅在size为2或更大时可用。

尽管如此,最好的解决方案可能是修改1000个函数,以便与其直接访问LegacyWidgetData的成员,然后调用一个自由函数。

double& b(LegacyWidgetData &data) { return data.b(); }
const double& b(const LegacyWidgetData &data) { return data.b(); }

然后您将为GenericWidgetData添加适当的重载。在这种情况下,使用static_assert进行验证可能会比较好:

template <std::size_t D> double& b(GenericWidgetData<D> &widgetData)
{
    static_assert(D > 1, "b is available only if D is big enough");
    return widgetData.data[D]; // Or an accessor function to avoid making it public
}

然后您还要添加const变体。

更多作品,但更灵活。例如,您可以正确验证每个函数的尺寸。

或者,另一种解决方案是为算法应用额外的参数,以应用转换。

template <typename Iterator, typename FA, typename FB>
void dummyFunction(Iterator begin, Iterator end, FA fa, FB fb)
{
  for (auto it = begin; it != end; it++)
  {
    cout << "Before: " << fa(*it) << "," << fb(*it) << "\t";
    fa(*it) += 1;
    fa(*it) -= 1;
    cout << "After: " << fa(*it) << "," << fb(*it) << "\n";
  }
}

这似乎需要很多工作,但是您会非常灵活。因此,也许您可​​以重新考虑要修改的代码。

现在进行这样的更改并在将来使用更简洁的代码可能比创建适配器或向GenericWidgetData添加许多仅由算法使用的功能要好。