引用单个元素或向量的函数

Function taking either reference to single element or a vector

我想要一个函数来修改传入的已知类型的元素,匹配我们在外循环中迭代的私有数据,将每个元素与传入的元素进行比较。数量很少,因此无需构建地图或优化它的 n² 性质。在伪代码中:

function fill_in_thing_data([in-out] things)
    for (item in private_items)
        info = wrangle(item)
        for (thing in things)
             if (matching(thing, info))
                 thing.data = info.data

说 private_items 对于迭代器或迭代设置来说很昂贵,所以我绝对希望在外循环中使用它。

简单吧?除了我想要两个共享一些底层代码的 C++ 重载函数,一个对事物进行非常量引用,一个对事物向量进行引用:

void fill_in_thing_data(Thing& single_thing);
void fill_in_thing_data(std::vector<Thing>& some_things);

在这两个函数之间共享代码的最佳方式是什么?我在想一个辅助函数,它接受迭代器或一些类似的序列类型,第一个函数传入由 1 元素构成的迭代器或序列,另一个由向量​​构成。我想使用 C++ 习惯用法,所以正在考虑使用 ForwardIterators 来实现。

问题:

这是我使用传入的访问者 lambda 和传回的访问 lambda 的丑陋解决方案,而不是迭代器,从而允许从共享函数中抽象出迭代逻辑:

using VisitThing = std::function<bool(Thing& thing)>;
using ThingVisitor = std::function<bool(VisitThing visit)>;

void match_things(ThingVisitor visitor);
void fill_in_thing_data(Thing& single_thing) {
    match_things([&](VisitThing visit) {
        visit(single_thing);
    });
}
void fill_in_thing_data(std::vector<Thing>& some_things) {
    match_things([&](VisitThing visit) {
        for (auto& thing : some_things) { visit(thing); }
    });
}

void match_things(ThingVisitor visitor) {
    auto stuff = fetch_private_stuff()
    while (item = stuff.get_next_item()) { // can't change this home-brew iteration
        auto item_info = wrangle(item) // but more complex, logic about skipping items etc
        visitor([&](Thing& thing) {
            if (item_info.token == thing.token)) {
                thing.data = item_info.data;
            }
        }
    }
}

我是不是错了,可以用迭代器做到这一点而不需要太复杂吗?或者我可以通过其他方式更好地做到这一点,比如一个好的数据结构 class 可以使用引用或向量构建,然后将其传入?或者像其他明显的东西我只是没有看到?谢谢!

我希望我的 OP 问题是正确的。对我来说,它归结为
有一个可以应用的功能

  • 单个引用以及
  • std::vector 个实例。

实际上有一个非常简单的解决方案,我什至(几十年前)在 C 中学习过,但也适用于 C++:

该函数接受一个指针和一个计数:

void fill(Thing *pThing, size_t len);

要在单个实例中使用它:

Thing thing;
fill(&thing, 1);

std::vector<Thing> 一起使用:

std::vector<Thing> things;
fill(&things[0], things.size());

恕我直言,除了 OP 提到迭代器这一事实之外,这有点像 C 风格。

那么,我们开始吧:

template <typename ITER>
void fill(ITER first, ITER last)
{
  for (const Item &item : items) {
    for (ITER iter = first; iter != last; ++iter) {
      if (matching(*iter, item)) iter->data = item;
    }
  }
} 

// a wrapper for a single thing
void fill(Thing &thing) { fill(&thing, &thing + 1); }

// a wrapper for a vector of things
void fill(std::vector<Thing> &things) { fill(things.begin(), things.end()); }

原理和上面一样,只是使用了迭代器。

完整演示:

#include <iostream>
#include <vector>

// an item
struct Item {
  int id = 0;
};

// the vector of OPs private items
std::vector<Item> items = {
  { 1 }, { 2 }, { 3 }
};

// a thing
struct Thing {
  int id;
  Item data;
  
  Thing(int id): id(id) { }
};

// a hypothetical function matching a thing with an item
bool matching(const Thing &thing, const Item &item)
{
  return thing.id == item.id;
}

// a generic fill (with matches) using iterators
template <typename ITER>
void fill(ITER first, ITER last)
{
  for (const Item &item : items) {
    for (ITER iter = first; iter != last; ++iter) {
      if (matching(*iter, item)) iter->data = item;
    }
  }
} 

// a wrapper for a single thing
void fill(Thing &thing) { fill(&thing, &thing + 1); }

// a wrapper for a vector of things
void fill(std::vector<Thing> &things) { fill(things.begin(), things.end()); }

// overloaded output operator for thing (demo sugar)
std::ostream& operator<<(std::ostream &out, const Thing &thing)
{
  return out << "Thing { id: " << thing.id
    << ", data: Item { id: " << thing.data.id << "} }";
}

// demo sugar
#define DEBUG(...) std::cout << #__VA_ARGS__ << ";\n"; __VA_ARGS__ 

// demonstrate
int main()
{
  // call to fill a single instance of Thing
  DEBUG(Thing thing(2));
  DEBUG(std::cout << thing << '\n');
  DEBUG(fill(thing));
  DEBUG(std::cout << thing << '\n');
  std::cout << '\n';
  // call to fill a vector of Thing
  DEBUG(std::vector<Thing> things = { Thing(2), Thing(3) });
  DEBUG(for (const Thing &thing : things) std::cout << thing << '\n');
  DEBUG(fill(things));
  DEBUG(for (const Thing &thing : things) std::cout << thing << '\n');
}

输出:

Thing thing(2);
std::cout << thing << '\n';
Thing { id: 2, data: Item { id: 0} }
fill(thing);
std::cout << thing << '\n';
Thing { id: 2, data: Item { id: 2} }

std::vector<Thing> things = { Thing(2), Thing(3) };
for (const Thing &thing : things) std::cout << thing << '\n';
Thing { id: 2, data: Item { id: 0} }
Thing { id: 3, data: Item { id: 0} }
fill(things);
for (const Thing &thing : things) std::cout << thing << '\n';
Thing { id: 2, data: Item { id: 2} }
Thing { id: 3, data: Item { id: 3} }

Live demo on coliru


关于带有迭代器的模板函数的注意事项:

最近我痛苦地意识到仅仅命名一些东西 ITER 并不足以证明它只接受迭代器。就我而言,我有多种重载,接受迭代器范围的重载只是其中之一。所以,我必须设法消除歧义。为此,我使用 SFINAE 找到了一个非常简单的解决方案(在 Stack Overflow 中):

template <typename ITER,
  typename = decltype(
    *std::declval<ITER&>(), void(), // has dereference
    ++std::declval<ITER&>(), void())> // has prefix inc.
void fill(ITER first, ITER last);

迭代器是(除其他外)必须提供取消引用和递增运算符的东西。 2nd 模板类型参数在其默认初始化中精确地检查了这一点。 (当然,SFINAE 的模板类型参数永远不应在模板实例中显式使用。)

Live demo on coliru