Подтвердить что ты не робот

С++ 11: Определение функции с параметром контейнера (например, для диапазона)?

Довольно часто в С++ 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 - тип массива, beginexpr и endexpr являются container и container + bound соответственно, где bound - это граница массива.

(B), если container является типом класса, unqualified-ids begin и end просматриваются в области класса container, как если бы поиск по элементам класса (3.4.5), и если либо (или оба) найдут хотя бы одно объявление, beginexpr и endexpr являются container.begin() и container.end() соответственно;

(C) в противном случае beginexpr и endexpr равны соответственно begin(container) и end(container), где начало и конец просматриваются с зависимым от аргумента поиска (3.4.2).

Можно ли определить набор перегрузок или специализаций addup, чтобы он обрабатывал четыре случая и не противоречил другим перегрузкам? Это, во-первых, регулярная функция пары итераторов, а затем каждая из A, B и C выше. Как?

(Если это возможно, то почему стандартная библиотека не предлагает такие перегрузки?)

Кроме того, что, если функция принимает дополнительные параметры за пределами контейнера? Можем ли мы модифицировать перегрузки таким образом, чтобы дополнительный добавочный параметр x (один со значением по умолчанию), добавленный ко всем из них, не делал следующие два вызова неоднозначными:

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

То есть мы можем статически утверждать (используя "SFINAE" или подобное), что параметр шаблона должен быть итератором, массивом, классом контейнера и т.д. - и использовать эту информацию для значений перегрузки?

4b9b3361

Ответ 1

Это то, что я сделал бы:

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

Он будет обрабатывать все важные случаи и правильно ли работает ADL. Я не уверен, что это эквивалентно тому, что используется для ранжирования, но, на мой взгляд, это лучшее решение.

следующие два вызова неоднозначны:

Я не компилировал, но я не вижу никакой двусмысленности там, если 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));
}

Ответ 2

Несколько случаев:

  • Если у вас нет других параметров, можно обрабатывать все случаи, используя 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

Если вы замените Thing на typename Range::value_type или int (и переместите его во вторую позицию в списке аргументов шаблона), тогда все перегрузки будут работать. Вы также можете указать Thing значение по умолчанию.