61

When creating a custom container class that plays by the usual rules (i.e. works with STL algorithms, works with well-behaved generic code, etc.), in C++03 it was sufficient to implement iterator support and member begin/end functions.

C++11 introduces two new concepts - range-based for loop and std::begin/end. Range-based for loop understands member begin/end functions, so any C++03 containers support range-based for out of the box. For algorithms the recommended way (according to 'Writing modern C++ code' by Herb Sutter) is to use std::begin instead of member function.

However, at this point I have to ask - is the recommended way to call a fully qualified begin() function (i.e. std::begin(c)) or to rely on ADL and call begin(c)?

ADL seems useless in this particular case - since std::begin(c) delegates to c.begin() if possible, usual ADL benefits do not seem to apply. And if everybody starts to rely on ADL, all custom containers have to implement extra begin()/end() free functions in their requisite namespaces. However, several sources seem to imply that unqualified calls to begin/end are the recommended way (i.e. https://svn.boost.org/trac/boost/ticket/6357).

So what is the C++11 way? Should container library authors write extra begin/end functions for their classes to support unqualified begin/end calls in absence of using namespace std; or using std::begin;?

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
zeuxcg
  • 9,216
  • 1
  • 26
  • 33
  • Note the Standard prefers member functions in the range-based for loop; if they cannot be found or the range-init is not of array or class type, the unqualified `begin` and `end` functions are used. The name lookup is explicitly mentioned in [stmt.ranged]/1: "`begin` and `end` are looked up with argument-dependent lookup (3.4.2). For the purposes of this name lookup, namespace `std` is an associated namespace." – dyp Jul 10 '13 at 05:46
  • Yes, but if the class does have begin/end member, then the lookup is not performed, right? So - correct me if I'm wrong - from the standard point of view ADL only applies for objects that do not look like standard containers, so it can't be used as a guidance in my case. – zeuxcg Jul 10 '13 at 05:53
  • 1
    Addendum: Note that the Standard for the range-based for statement does *not* use unqualified name lookup, but explicitly *argument-dependent* lookup. I tested this with clang++3.2 to support my interpretation: Global `begin`/`end` functions are not found for the range-based for statement if the type of the range-init expression is a class type that's declared in a namespace. I don't know how you could emulate that with your own code. – dyp Jul 10 '13 at 05:54
  • Yes, the ADL is only performed if either the expression is not of class or array type or it is of class type but no `begin`/`end` member functions can be found. – dyp Jul 10 '13 at 05:55
  • 2
    Another remark ;) If you use the qualified version `std::begin(c)`, you implicitly require `c` to either be of array type or to have `begin`/`end` member functions. The latter is due to the declaration of `begin(c)` having the return type `decltype(c.begin())`: you cannot change the return type by function template specialization and you're not allowed to overload `std::begin` in the `std` namespace. – dyp Jul 10 '13 at 06:23
  • Yes, that's correct. However I'm not sure that this is a problem. That's sort of what the question is about - is there a case where calling std::begin causes issues, i.e. incompatibility with some custom types or lack of useful extension points? – zeuxcg Jul 10 '13 at 06:44

1 Answers1

36

There are several approaches, each with their own pros and cons. Below three approaches with a cost-benefit analysis.

ADL through custom non-member begin() / end()

The first alternative provides non-member begin() and end() function templates inside a legacy namespace to retrofit the required functionality onto any class or class template that can provide it, but has e.g. the wrong naming conventions. Calling code can then rely on ADL to find these new functions. Example code (based on comments by @Xeo):

// LegacyContainerBeginEnd.h
namespace legacy {

// retro-fitting begin() / end() interface on legacy 
// Container class template with incompatible names         
template<class C> 
auto begin(Container& c) -> decltype(c.legacy_begin())
{ 
    return c.legacy_begin(); 
}

// similarly for begin() taking const&, cbegin(), end(), cend(), etc.

} // namespace legacy

// print.h
template<class C>
void print(C const& c)
{
    // bring into scope to fall back on for types without their own namespace non-member begin()/end()
    using std::begin;
    using std::end;

    // works for Standard Containers, C-style arrays and legacy Containers
    std::copy(begin(c), end(c), std::ostream_iterator<decltype(*begin(c))>(std::cout, " ")); std::cout << "\n";

    // alternative: also works for Standard Containers, C-style arrays and legacy Containers
    for (auto elem: c) std::cout << elem << " "; std::cout << "\n";
}

Pros: consistent and terse calling convention that works completely generically

  • works for any Standard Container and user-types that define member .begin() and .end()
  • works for C-style arrays
  • can be retrofitted to work (also for range-for loops!) for any class template legacy::Container<T> that does not have member .begin() and end() without requiring source code modifications

Cons: requires using-declarations in many places

  • std::begin and std::end are required to have been brought into every explicit calling scope as fall back options for C-style arrays (potential pitfall for template headers and general nuisance)

ADL through custom non-member adl_begin() and adl_end()

A second alternative is to encapsulate the using-declarations of the previous solution into a separate adl namespace by providing non-member function templates adl_begin() and adl_end(), which can then also be found through ADL. Example code (based on comments by @Yakk):

// LegacyContainerBeginEnd.h 
// as before...

// ADLBeginEnd.h
namespace adl {

using std::begin; // <-- here, because otherwise decltype() will not find it 

template<class C> 
auto adl_begin(C && c) -> decltype(begin(std::forward<C>(c)))
{ 
    // using std::begin; // in C++14 this might work because decltype() is no longer needed
    return begin(std::forward<C>(c)); // try to find non-member, fall back on std::
}

// similary for cbegin(), end(), cend(), etc.

} // namespace adl

using adl::adl_begin; // will be visible in any compilation unit that includes this header

// print.h
# include "ADLBeginEnd.h" // brings adl_begin() and adl_end() into scope

template<class C>
void print(C const& c)
{
    // works for Standard Containers, C-style arrays and legacy Containers
    std::copy(adl_begin(c), adl_end(c), std::ostream_iterator<decltype(*adl_begin(c))>(std::cout, " ")); std::cout << "\n";

    // alternative: also works for Standard Containers, C-style arrays and legacy Containers
    // does not need adl_begin() / adl_end(), but continues to work
    for (auto elem: c) std::cout << elem << " "; std::cout << "\n";
}

Pros: consistent calling convention that works completely generically

  • the same pros as for @Xeo's suggestion +
  • the repeated using-declarations have been encapsulated (DRY)

Cons: a little verbose

  • adl_begin() / adl_end() is not as terse as begin() / end()
  • it is perhaps also not as idiomatic (although it is explicit)
  • pending C++14 return type deduction, will also pollute namespace with std::begin / std::end

NOTE: Not sure if this really improves upon the previous approach.

Explicitly qualifying std::begin() or std::end() everywhere

Once the verbosity of begin() / end() has been given up anyway, why not go back to the qualified calls of std::begin() / std::end()? Example code:

// LegacyIntContainerBeginEnd.h
namespace std {

// retro-fitting begin() / end() interface on legacy IntContainer class 
// with incompatible names         
template<> 
auto begin(legacy::IntContainer& c) -> decltype(c.legacy_begin())
{ 
    return c.legacy_begin(); 
}

// similary for begin() taking const&, cbegin(), end(), cend(), etc.

} // namespace std

// LegacyContainer.h
namespace legacy {

template<class T>
class Container
{
public:
    // YES, DOCUMENT REALLY WELL THAT THE EXISTING CODE IS BEING MODIFIED
    auto begin() -> decltype(legacy_begin()) { return legacy_begin(); }
    auto end() -> decltype(legacy_end()) { return legacy_end(); }

    // rest of existing interface
};

} // namespace legacy

// print.h
template<class C>
void print(C const& c)
{
    // works for Standard Containers, C-style arrays as well as 
    // legacy::IntContainer and legacy::Container<T>
    std::copy(std::begin(c), std::end(c), std::ostream_iterator<decltype(*std::begin(c))>(std::cout, " ")); std::cout << "\n";

    // alternative: also works for Standard Containers, C-style arrays and
    // legacy::IntContainer and legacy::Container<T>
    for (auto elem: c) std::cout << elem << " "; std::cout << "\n";
}

Pros: consistent calling convention that works almost generically

  • works for any Standard Container and user-types that define member .begin() and .end()
  • works for C-style arrays

Cons: a little verbose and retrofitting is not generic and a maintainence problem

  • std::begin() / std::end() is a little more verbose than begin() / end()
  • can only be retrofitted to work (also for range-for loops!) for any class LegacyContainer that does not have member .begin() and end() (and for which there is no source code!) by providing explicit specializations of the non-member function templates begin() and end() in namespace std
  • can only be retrofitted onto class templates LegacyContainer<T> by directly adding member functions begin() / end() inside the source code of LegacyContainer<T> (which for templates is available). The namespace std trick does not work here because function templates cannot be partially specialized. 

What to use?

The ADL approach through non-member begin() / end() in a a container's own namespace is the idiomatic C++11 approach, especially for generic functions that require retrofitting on legacy classes and class templates. It is the same idiom as for user-providing non-member swap() functions.

For code that only uses Standard Containers or C-style arrays, std::begin() and std::end() could be called everywhere without introducing using-declarations, at the expense of more verbose calls. This approach can even be retrofitted but it requires fiddling with namespace std (for class types) or in-place source modifcations (for class templates). It can be done, but is not worth the maintainence trouble.

In non-generic code, where the container in question is known at coding-time, one could even rely on ADL for Standard Containers only, and explicitly qualify std::begin / std::end for C-style arrays. It loses some calling consistency but saves on using-declarations.

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • Thank you for the answer; that's what I'm thinking as well (for context, my question comes as a response to this https://code.google.com/p/pugixml/issues/detail?id=170). I think this is the right stance here; I'll wait for some time in case there is any extra input. – zeuxcg Jul 10 '13 at 07:23
  • @zeuxcg I updated my answer to reflect the using declaration approach. – TemplateRex Jul 10 '13 at 07:32
  • 5
    I’m not convinced (in fact, I think you’re wrong) – the idiom `using std::swap; swap(a, b);` is firmly established in C++ (it’s *the* correct way of invoking `swap` in a generic context) … you haven’t explained why the same isn’t true for `using std::begin;`. – Konrad Rudolph Jul 10 '13 at 07:37
  • 2
    @KonradRudolph But `swap` can benefit from user-defined optimizations, as I wrote above, so you really want to call swap unqualified and fall back to the `std::swap`. But where is the scope for optimization in a getter like `begin`? In contrast, the `using std::begin;` is used here ONLY to save typing, not to provide a user-defined implementation with a fall back to the `std::begin`. – TemplateRex Jul 10 '13 at 07:39
  • @jogojapan The main problem I have with unqualified calls is that it forces people who write containers to be aware of the fact that somebody prefers unqualified calls and to add free begin/end functions. Also, if your code style involves getting rid of std:: then it's likely that you have a using namespace std; in your code so begin() does not need ADL to resolve to std::begin. – zeuxcg Jul 10 '13 at 07:55
  • 4
    "I would not recommend this approach" -- I would. It is the correct way to use `std::begin` and `std::end`, everything else is completely *useless* in **generic** code as free `begin` and `end` functions will not be found otherwise, and as such containers without member begin/end will not work with your code. – Xeo Jul 10 '13 at 08:04
  • @Xeo If you write your own container, you can already give it a member `begin()` and `end()`, and it will already work out-of-the-box with `std::begin()` and `std::end()`. So why also use your own non-member `begin()` / `end()`? The container requirements mandate member `begin()` / `end()`, so why would you ever write a container without member functions `begin()` / `end()`? – TemplateRex Jul 10 '13 at 08:06
  • 6
    And what if it's not your own container? What if it doesn't have a fitting interface, but provides other means that can get you equivalent behaviour, *if only one could add `begin`/`end` to the interface?* Qt did it right and provides those next to its own iteration facilities, but not every library had such a foresight. – Xeo Jul 10 '13 at 08:09
  • 3
    @Xeo the D programming language has Uniform Function Call Syntax that will automatically look for non-member functions `fun(c)` if a member function `c.fun()` cannot be found (and vice versa). This would be really cool to have in C++ – TemplateRex Jul 10 '13 at 08:36
  • @TemplateRex Stroustrup mentioned that he wants that in C++, so it might happen. – Cubbi Jul 10 '13 at 14:19
  • 2
    I would not recommend this approach. Instead, I would `namespace adl { using std::begin; template auto adl_begin( C&& c )->decltype( begin(std::forward(c)) ) { return begin(std::forward(c)); } } using adl::adl_begin;` and likewise for `end`. By using this technique, I can call adl-qualified `begin` with `std::begin` considered without having to always `using std::begin` at every spot I need to do it, which is not possible without pollution in some contexts (like `auto->` return type deduction). Now I can do ADL when I want to, and the fact I'm doing ADL is clear. – Yakk - Adam Nevraumont Jul 10 '13 at 14:19
  • The updated version indeed makes sense for legacy containers - qualified call for begin essentially prevents iterating through them without changing the container or violating the standard by overloading std::begin. Interestingly, all proposed solutions do not involve modifying C++03 compatible containers in any way - I think that answers the original question. – zeuxcg Jul 10 '13 at 14:54
  • @Yakk Very interesting remark! I have adapted my answer to show it as one of three alternatives. I am opinionated towards using qualified calls everywhere, but that would depend on the amount of legacy template code that is likely to appear in generic code. – TemplateRex Jul 10 '13 at 19:00
  • @Cubbi do you have a link to that statement by Stroustrup? – TemplateRex Jul 10 '13 at 19:00
  • @DyP in C++14 the decltype is no longer needed, correct? so a plain `auto` return type will do the right thing? – TemplateRex Jul 10 '13 at 19:27
  • @Xeo, it doesn't look to me like he's adding new functions or overloads to `namespace std`. It just looks like he's adding specializations for existing functions. That is allowed under the standard for user-defined types, provided the specializations meet the same requirements that the standard mandates for the main template. I personally don't care for that approach, as I feel ADL is the best way to leverage the "interface principle", and it doesn't work for partial specializations, since they're not available. But what is written in the post looks legal. – Adam H. Peterson Jul 10 '13 at 19:53
  • @Adam: IIRC, the standard only allows that for specific types - or maybe that was the `` part only. In any case, as soon as the legacy container is a template, you can't use that way anyways. – Xeo Jul 10 '13 at 20:13
  • 2
    @Xeo upon further reflection, I have to agree with you again (I have been changing my mind quite a bit today, I know, so hopefully for good now) that the ADL approach is the idiomatic solution here. Tnx for insisting :-) – TemplateRex Jul 10 '13 at 20:43
  • @Xeo, the draft copy of the C++11 standard I have says in 17.6.4.2.1.1 (Namspace std): "A program may add a template specialization for any standard library template to namespace std only if the declaration depends on a user-defined type and the specialization meets the standard library requirements for the original template and is not explicitly prohibited." – Adam H. Peterson Jul 10 '13 at 21:12
  • @sehe, LOL it's community wiki now, so won't get me rep, but thanks anyway :-) – TemplateRex Jul 10 '13 at 21:12
  • @Cubbi tnx! but the reverse (`x.f()` finding `f(x)`) would be even nicer to have since it would eliminate this whole retrofitting stuff – TemplateRex Jul 10 '13 at 21:26
  • @Xeo, I agree with you WRT specializing for templates (because of the partial specialization issue). That's one of several reasons why I think ADL is the way to go. I was just noting that what the answer had wasn't actually disallowed. – Adam H. Peterson Jul 10 '13 at 21:27
  • @Adam: Right, as I said later, I think I've mixed it up with the rules regarding ``, where only certain templates are allowed to be (partially) specialized. – Xeo Jul 10 '13 at 22:02
  • Two comments: a) I am going to remove my earlier comments; they don't apply any more. b) Your `legacy::begin` of the first solution is defined for `const &C`, so you've actually implemented `cbegin` rather than `begin`. – jogojapan Jul 11 '13 at 02:48
  • 1
    @Dyp Yes. I didn't mean that `cbegin()` should be used. I just pointed out that the implementation takes the argument by const-reference, so it will always return a `const_iterator`. In this sense, it is _effectively_ the same as `cbegin()`. – jogojapan Jul 11 '13 at 07:18
  • 1
    @jogojapan added comments that these overloads should be included, but I don't want to write it because the answer is long enough as it is. tnx for your suggestion. – TemplateRex Jul 11 '13 at 07:59
  • The last part of the question was for implementers of containers: "Should container library authors write extra begin/end functions for their classes to support unqualified begin/end calls in absence of using namespace std; or using std::begin;?" Is there advice available here? – mabraham Jan 15 '19 at 16:09