14

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?

Community
  • 1
  • 1
Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319
  • 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. Nov 25 '12 at 05:02
  • 1
    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 Nov 25 '12 at 05:11
  • 1
    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 Nov 25 '12 at 05:27
  • The idiom for adding elements is an OutputIterator. – Andrew Tomazos Nov 25 '12 at 05:44
  • Note: `for (auto t : f())` will call `f()` again and again and again... This is quite certainly not what you want! – dani Mar 16 '17 at 21:31

2 Answers2

11

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));
}
Pubby
  • 51,882
  • 13
  • 139
  • 180
  • You probably want to take the range by reference (i.e. it is a container, making a copy can be expensive) – David Rodríguez - dribeas Nov 25 '12 at 04:58
  • Do `begin(range)` and `end(range)` handle regular arrays (case A)? – Andrew Tomazos Nov 25 '12 at 04:58
  • @AndrewTomazos-Fathomling Yes. – Pubby Nov 25 '12 at 04:59
  • 1
    @AndrewTomazos-Fathomling Argument dependent lookup. The `using ...` is really just to allow `begin` to be specialized/overloaded in namespaces other than `std` and still work in your function. – Pubby Nov 25 '12 at 05:01
  • If `std::begin` and `std::end` have all the desired properties than I wonder why the standard doesn't define _range based for_ in terms of them (ie why 3 cases, they could just specify the final one) – Andrew Tomazos Nov 25 '12 at 05:38
  • @AndrewTomazos-Fathomling See this: http://stackoverflow.com/questions/2648878/range-based-for-statement-definition-redundancy – Pubby Nov 25 '12 at 05:39
  • 1
    A C++03-ish alternative is using Boost.Range and using `boost::begin`/`boost::end` qualified -- they do ADL on your behalf and are able to deal with arrays. It is also possible to write your own `adl::begin`. – Luc Danton Nov 25 '12 at 09:02
  • @Pubby : A quick question regarding "The using ... is really just to allow begin to be specialized/overloaded in namespaces other than std and still work in your function." Why not use unqualified "begin" and "end" (just as in your example) but without using the using-declarations? Wouldn't ADL just do the right thing -- i.e., for Range corresponding to std:: type, I'd presume the std::begin is picked anyway, and for the namespaces other than std ADL would work already. What am I missing? – Matt Feb 06 '13 at 16:45
  • 1
    Self-reply: I'm guessing this is so that we can also use types with member-functions `begin` and `end` but without nonmember `begin` and `end`. // Based on http://stackoverflow.com/a/11242664/859774 – Matt Feb 06 '13 at 17:54
  • ADL rules dectact that in `begin(ns::obj)` if begin is not defined in namespace of the caller but if its defined in ns then it gets used. When you put using std::begin it means that if begin is defined in std then it takes precedent before looking up ns. This means that we want to give std::begin priority over begin method defined in ns. This is generally good idea as we are essentially doing duck typing here and there may be some legacy code with different begin() definition that can give unexpected behavior. – Shital Shah May 11 '16 at 23:43
3

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.

J.N.
  • 8,203
  • 3
  • 29
  • 39