14

在 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) ifcontainer是一个数组类型,beginexpr并且endexpr分别是 arecontainercontainer+ boundbound数组绑定在哪里。

(B) 如果container是类类型,则在类的范围内查找 unqualified-idsbegin和就像通过类成员访问查找 (3.4.5) 一样,并且如果其中一个(或两者)找到至少一个声明,并且是container.begin() 和 container.end() 分别;endcontainerbeginexprendexpr

(C) 否则,beginexpr分别endexprbegin(container)end(container),其中 begin 和 end 使用参数相关查找 (3.4.2) 进行查找。

是否可以定义一组重载或特化addup来处理这四种情况,而不与其他重载冲突?那首先是一个常规的迭代器对函数,然后是上面的 A、B 和 C 中的每一个。如何?

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

另外,如果函数在容器之外接受额外的参数怎么办?我们是否可以修改重载,使添加到所有重载的可选额外参数 x(具有默认值的参数)不会使以下两个调用产生歧义:

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

那就是我们可以静态断言(使用“SFINAE”或类似的)模板参数必须是一个迭代器、一个数组、一个容器类等 - 并将这些信息用于重载消歧?

4

2 回答 2

11

这就是我要做的:

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

它将处理所有重要案例并正确执行 ADL。我不确定它是否等同于 ranged-based-for 所做的,但我认为这是最好的解决方案。

以下两个电话不明确:

我还没有编译,但除非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));
}
于 2012-11-25T04:57:01.183 回答
3

几个案例:

  • 如果您没有任何其他参数,则可以使用std::begin和处理所有情况std::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

如果您用 or 替换Thingtypename Range::value_type并将int其移动到模板参数列表中的第二个位置),那么所有重载都将起作用。你也可以给Thing一个默认值。

于 2012-11-25T05:23:48.667 回答