11

C++0x's ranged-for loop has a special exception to handle arrays (FDIS §6.5.4), and there are two functions, std::begin and end, which are overloaded to handle arrays or to select begin/end methods. This leads me to believe a function accepting a generic sequence could be written to match a ranged-for loop's behavior:

template<class C>
void f(C &c) {
  using std::begin;
  using std::end;
  do_something_with(begin(c), end(c));
}

If there's a "more specific" begin/end in the namespace of C, it will be selected through ADL, otherwise the code "defaults" to std::begin/end.

However, there is a reason ranged-for has that special exception. If passing an array of a type in a namespace with a semantically-different begin/end which takes a pointer, the array forms of std::begin/end aren't selected:

namespace ns {
  struct A {};
  void begin(A*);  // Does something completely different from std::begin.
}

void f_A() {  // Imagine above f() called with an array of ns::A objects.
  ns::A c[42];
  using std::begin;
  begin(c);  // Selects ns::begin, not array form of std::begin!
}

To to avoid this, is there a better solution than writing my own begin/end wrappers (which use ADL internally) and calling them explicitly instead of either std::begin or an ADLized begin?

namespace my {
  template<class T>
  auto begin(T &c)  // Also overload on T const &c, as std::begin does.
  -> decltype(...)  // See below.
  {
    using std::begin;
    return begin(c);
  }

  template<class T, int N>
  T* begin(T (&c)[N]) {
    return c;
  }
}
// my::end omitted, but it is analogous to my::begin.

template<class C>
void f(C &c) {
  do_something_with(my::begin(c), my::end(c));
}

However, as shown by the ellipsis above, I don't even know how to write my::begin! How can I, for that decltype, select the type that will be selected through a local using-declaration and ADL?

Fred Nurk
  • 13,952
  • 4
  • 37
  • 63
  • It took me about 30 minutes to think about and write up this question, but it's something that's been brewing in my head for a while, and I think just writing it gave me an idea to solve the "decltype considering local using-declaration", but it's not pretty. I'll self-answer if no one mentions it sometime today. – Fred Nurk May 18 '11 at 13:01
  • I think there's another problem: wouldn't my::begin be considered for "return begin(c);", and, if selected, be infinite recursion? – Fred Nurk May 18 '11 at 13:05
  • @Fred: this second problem is simple and requires just an additional function wrapper in a separate "detail" namespace. – Alexandre C. May 18 '11 at 13:09
  • How about returning `typename T::iterator`? – Xeo May 18 '11 at 13:10
  • @Xeo: I don't want to require that nested type, and solving this problem without that will be applicable to other situations. – Fred Nurk May 18 '11 at 13:12
  • @AlexandreC.: Yeah, I'm thinking along those lines. However, it's an additional function wrapper *per overload* and it can't be in a nested namespace, otherwise my::detail::begin can select my::begin, as my::begin is in scope in my::detail. – Fred Nurk May 18 '11 at 13:13
  • 1
    Personally, I'd look for a trait - something like `typename range_traits::iterator`. That would require the definition of the `range_traits` class, but a `range_traits` class that handles the general case wouldn't be too difficult to write.. (too bad we don't have concepts in c++0x anymore) – rlc May 18 '11 at 13:33

2 Answers2

4

I've encountered the same situation while using tuples:

template<typename Tuple>
auto f(Tuple&& tuple)
-> /* ??? */
{
    using std::get;
    return get<Idx>(tuple);
}

which accepts both std::tuple and boost::tuple, and accepts both lvalues and rvalues as opposed to template<typename... Types> auto f(std::tuple<Types...>& tuple) -> /* ??? */.

This particular case was solved with a traits class, which is in fact provided by the Standard: std::tuple_element. As usual with traits classes, the idea is that tuple is a protocol and anything that want to conform to it will provide a specialization for e.g. tuple_element. So in my case the solution already existed.

In your case, if you were writing a library, I'd recommend writing (and documenting) such a traits class. In application code or other situations, I'm not so sure.

Luc Danton
  • 34,649
  • 6
  • 70
  • 114
  • Traits sounds like a good idea, but I'm not sure at all how to apply it to this situation in a way that doesn't require a specialization per each type to be passed to my::begin. – Fred Nurk May 18 '11 at 13:30
1

You can special-case the arrays yourself. The type of an array is (and has to be for begin/end to work) ElementType (&)[Size], so if you overload the function like:

template<class C, size_t S>
void f(C (&c)[S]) {
  do_something_with(std::begin(c), std::end(c));
}

it should behave specially like the for-loop.

On a side-note, you don't need std::begin and std::end then, they are trivial:

template<class C, size_t S>
void f(C (&c)[S]) {
  do_something_with(c, c + S);
}

(may need a cast; I actually only used it with things that demanded pointers, not any iterators).

On another side-note, begin and end functions taking pointers are rather silly thing to do. If the pointed object is a collection, they should probably be taking reference instead.

Jan Hudec
  • 73,652
  • 13
  • 125
  • 172
  • I plan to special-case an array parameter myself, but using my::begin so I only have to do it once rather than overload functions f, g, h, i, j... In all of those functions, I can just call my::begin. – Fred Nurk May 18 '11 at 13:45
  • "begin and end functions taking pointers are rather silly thing to do" Is it silly? Maybe, but how can I write a function ("f" in the question) that doesn't randomly break (it still might compile and run!) if someone happens to pass a type in an unknown-to-me namespace that has such a begin or end? – Fred Nurk May 18 '11 at 16:39