C++11:用容器参数定义函数(类似于基于范围的)

C++11: Defining Function With Container Parameter (like range-based for)?

本文关键字:类似于 于范围 范围 函数 定义 参数 C++11      更新时间:2023-10-16

在C++11中,我经常需要定义一个将容器作为参数的函数。

例如,让我们定义一个函数addup(是的,只是std::accumulate的一个简单版本):

template <class I>
int addup (I first, I last)
{
int x = 0;
while ( first != last )
x += *first++;
return x;
}

这需要一个迭代器范围,它是灵活的,也是标准的库习惯用法。

然而,假设我有一个函数:

vector<T> f();

我必须这样做:

auto v = f();
int x = addup(v.begin(), v.end());

我宁愿这样做:

int x = addup(f());

就像我能做到的那样:

for (auto t : f())
...

本着基于范围的精神,我想要这样的东西:

template<class C>
int addup(C&& container)
{
addup(beginexpr(container), endexpr(container)); // ???
}

在标准6.5.4中(转述):

(A)如果container是数组类型,则beginexprendexpr分别是containercontainer+bound,其中bound是数组绑定。

(B) 如果container是类类型,则在类container的作用域中查找不合格idbeginend,就像通过类成员访问查找一样(3.4.5),并且如果其中一个(或两者)找到至少一个声明,则beginexprendexpr分别是container.begin()和container.end();

(C) 否则,beginexprendexpr分别为begin(container)end(container),其中开始和结束是通过自变量相关查找查找的(3.4.2)

是否可以定义addup的一组重载或专门化,使其能够处理这四种情况,而不与其他重载冲突?这首先是一个正则迭代器对函数,然后是上面的a、B和C中的每一个。怎样

(如果这是可能的,为什么标准库不提供这样的过载?)

此外,如果函数在容器之外获取额外的参数,该怎么办?我们是否可以修改重载,使添加到所有重载中的可选额外参数x(一个具有默认值的参数)不会使以下两个调用不明确:

addup(v.begin(), v.end());
addup(v, x);

也就是说,我们可以静态地断言(使用"SFINAE"或类似的方法)模板参数必须是迭代器、数组、容器类等,并将这些信息用于过载消除歧义吗?

这就是我要做的:

template<class Range>
int addup(Range&& range)
{
using std::begin;
using std::end;
addup(begin(range), end(range));  // begin(), NOT std::begin()  (ADL)
}

它将处理所有重要的病例,并正确地进行ADL。我不确定它是否等同于基于range-based的功能,但在我看来,这是最好的解决方案。

以下两个调用不明确:

我还没有编译,但除非x需要隐式转换,否则我看不到任何歧义。您还可以使用boost::make_iterator_range,避免迭代器参数过载。


我认为这也会起作用:

template<class Range>
int addup(Range&& range)
{
int x = 0;
for(auto&& v : range)
x += v;
return x; 
}
template <class I>
int addup (I first, I last)
{
return addup(boost::make_iterator_range(first, last));
}

几种情况:

  • 如果没有任何其他参数,则可以使用std::beginstd::end处理所有情况
  • 还有一个额外的参数,其类型不是模板,或者依赖于您的范围/迭代器(例如T::value_type将同时在范围和迭代器上工作),或者具有默认值。那就没有问题了
  • 还有一个附加参数,它与范围无关,其类型是模板,没有默认值。然后,在调用函数时,如果不手动指定该类型,就无法执行此操作

下面是一个例子:

template <class Thing, class Iterator>
void DoStuff(Iterator first, Iterator last, Thing thing = Thing())
{ ... }
template <class Thing, class Range>
void DoStuff(Range& range, Thing thing = Thing())
{ ... }

vector<int> coin = {1,2,3,4};
DoStuff(coin, 123); // OK
DoStuff(begin(coin), end(coin), 123); // OK
DoStuff<int>(coin); // OK
DoStuff<int>(begin(coin), end(coin)); // OK
DoStuff(coin); // !! KO
DoStuff(begin(coin), end(coin)); // !! KO

如果用typename Range::value_typeint替换Thing(并将其移动到模板参数列表中的第二个位置),则所有重载都将起作用。您也可以为Thing指定一个默认值。