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



我有以下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的迭代者,而不涉及任何中间对象的缓存/创建。如果这可以在编译时使用模板元编程完成,那就太棒了!

您可以为D == 2时的GenericWidgetData添加一个模板专用化,然后实现.a().b()函数以匹配LegacyWidgetData的接口。

即使可以编写一个迭代器适配器,它可以在编译时通过一些元编程技巧进行切换,但除了它自己的编写者之外,任何人都很难理解它。

如果您有迭代器适配器,那么您通常需要两个这样的对象。

如果你有很多这样的算法,那么将向量隐藏在另一个类中是有意义的,该类的begin/end方法将返回适配器。或多或少类似的东西:

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

然而,简单地向GenericWidgetData添加适当的函数可能是有意义的。在这种情况下,您可能需要一些专业化,例如b()只有在大小为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添加许多仅用于算法的函数要好。

最新更新