引用单个元素或向量的函数
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 来实现。
问题:
- 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;
}
}
}
}
我是不是错了,可以用迭代器做到这一点而不需要太复杂吗?或者我可以通过其他方式更好地做到这一点,比如一个好的数据结构 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} }
关于带有迭代器的模板函数的注意事项:
最近我痛苦地意识到仅仅命名一些东西 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 的模板类型参数永远不应在模板实例中显式使用。)
我想要一个函数来修改传入的已知类型的元素,匹配我们在外循环中迭代的私有数据,将每个元素与传入的元素进行比较。数量很少,因此无需构建地图或优化它的 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;
}
}
}
}
我是不是错了,可以用迭代器做到这一点而不需要太复杂吗?或者我可以通过其他方式更好地做到这一点,比如一个好的数据结构 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} }
关于带有迭代器的模板函数的注意事项:
最近我痛苦地意识到仅仅命名一些东西 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 的模板类型参数永远不应在模板实例中显式使用。)