【问题标题】:Function taking either reference to single element or a vector引用单个元素或向量的函数
【发布时间】:2021-05-27 11:12:18
【问题描述】:

我想要一个函数来修改传入的已知类型元素,匹配我们在外部循环中迭代的私有数据,将每个元素与传入的元素进行比较。数量很小,因此无需构建地图或优化 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 来做。

问题:

  • C++ 迭代器通常并不容易。好的,使用它们没问题,但是编写一个将任何类型的 ForwardIterator 简单地转换为已知类型的函数似乎出乎意料地棘手。
  • 对单个元素引用的特殊情况迭代器是否可用?还是很容易定义的东西?似乎答案是否定的。

这是我使用传入的访问者 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;
            }
        }
    }
}

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

【问题讨论】:

    标签: c++ lambda stl iterator


    【解决方案1】:

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

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

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

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

    void fill(Thing *pThing, size_t len);
    

    将其与单个实例一起使用:

    Thing thing;
    fill(&thing, 1);
    

    std::vector&lt;Thing&gt;一起使用:

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

    恕我直言,这在某种程度上是 C-ish,除此之外,OP 提到了迭代器。

    那么,我们开始吧:

    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

    【讨论】:

    • 完美。在我的一生中,我没有发现任何关于使用迭代器的基础知识提到定义接受它们的新函数。每一个更具体的搜索似乎都涉及到关于 SFINAE 的杂草,但是你的例子比我发现的任何东西都简单。非常感谢!
    • 后续:在模板参数中,是否可以声明 ITERThing 类型的迭代器?关于不仅实现取消引用而且返回类型Thing&amp; 的东西?咆哮:我仍然没有找到关于上述decltype 语法如何工作的好参考,例如void() 的含义。综合文档在哪里?我所看到的只是如何使用typename = decltype... 执行1 件事,或使用enable_if 执行1 件事。并且总是在类型 T 上,好像每个人都只写通用排序算法。我所做的一些搜索this SO是热门搜索。
    • @JohnHouston void() 是没有参数的函数类型,返回类型为void。我必须承认,我不知道为什么要使用它,也不知道如果 void() 被排除在外会发生什么。如果您希望 ITER 是 Thing 类型的迭代器,我会尝试使用 std::is_same 并使用取消引用的返回类型。请尝试google 'site:stackoverflow.com c++ sfinae iterator' 查找更多信息。我在第一次点击中看到了一些有价值的 Q/A。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-24
    • 2010-12-11
    • 2018-03-15
    • 1970-01-01
    相关资源
    最近更新 更多