14
votes

Quite frequently in C++11 I need to define a function that takes a container as a parameter.

For example lets define a function addup (yes just a simple version of std::accumulate):

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

This takes an iterator range, which is flexible and the standard library idiom.

However suppose I have a function:

vector<T> f();

I have to do this:

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

I would rather just do this:

int x = addup(f());

Just like I can do this:

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

In the spirit of range-based for I would like something like this:

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

In the standard it says in 6.5.4 (paraphrasing):

(A) if container is an array type, beginexpr and endexpr are container and container + bound, respectively, where bound is the array bound.

(B) if container is a class type, the unqualified-ids begin and end are looked up in the scope of class container as if by class member access lookup (3.4.5), and if either (or both) finds at least one declaration, beginexpr and endexpr are container.begin() and container.end(), respectively;

(C) otherwise, beginexpr and endexpr are begin(container) and end(container), respectively, where begin and end are looked up with argument-dependent lookup (3.4.2).

Is it possible to define a set of overloads or specializations of addup such that it will handle the four cases, and not conflict with other overloads? That is firstly a regular iterator pair function, and then each of A, B and C above. How?

(If this is possible than why doesn't the standard library offer such overloads?)

Also, what if the function takes extra parameters beyond the container? Can we modify the overloads in such a way that an optional extra parameter x (one with a default value) added to all of them will not make the following two calls ambiguous:

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

That is can we statically assert (using "SFINAE" or similar) that the template parameter has to be an iterator, an array, a container class, etc - and have this information used for overload disambiguation?

2
I can't recall where, but I've seen somewhere that the standard guys ran into a similar problem while trying to integrate ranges in C++, which is likely to explain why they don't offer it yet. - J.N.
You could just do away with the iterator based versions (require they wrap the stuff up in a range), or give them different names. Without something like fast concepts, throwing too much SFINAE at problems can make your code unreadable. Functions need to have the same name when you want to do static dispatch between them, and I don't think that this is the case for your problem... - Yakk - Adam Nevraumont
Do you really need a container (i.e. ability to add/remove elements) or do you just need a range (whose elements you can sometimes edit)? Or do you simply need it because it's easier to pass in a single parameter instead of two? Your code shows the need for a range, which Boost.Range is perfect for, but you're asking about containers... which is a question whose answer I'd also be interested in. :) - user541686
The idiom for adding elements is an OutputIterator. - Andrew Tomazos
Note: for (auto t : f()) will call f() again and again and again... This is quite certainly not what you want! - dani

2 Answers

11
votes

This is what I would do:

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

It will handle all the important cases and does ADL properly. I'm not sure if it's equivalent to what ranged-based-for does but in my opinion it is the best solution.

the following two calls ambiguous:

I haven't compiled, but I don't see any ambiguity there unless x needs an implicit conversion. You can also use boost::make_iterator_range and avoid having an iterator parameter overload.


I think this will also work:

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));
}
3
votes

A few cases:

  • If you do not have any other parameters it is possible to handle all cases using std::begin and std::end
  • There's an additional parameter whose type is not a template or is dependent on your range/iterator (for instance T::value_type will work both on the range and on the iterators) or has a default value. Then there's no problem again.
  • There's an additional parameter whose is unrelated to the range and whose type is a template and has no default. Then you can't do it without specifying the manually that type while calling your function.

Here is an example:

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

If you replace Thing by typename Range::value_type or int (and move it to the second position in the template argument list) then all overloads will work. You can also give Thing a default value.