12

I'm trying to specialize std::begin for a custom container. I'm doing this because I want to use range-based for with the container. This is what I have:

class stackiterator { … };
class stack { … };

#include <iterator>

template <> stackiterator std::begin(stack& S)
{
  return S.GetBottom();
}

I get the following error at the definition of my begin specialization:

No function template matches function template specialization 'begin'

What am I doing wrong?

Emil Laine
  • 41,598
  • 9
  • 101
  • 157
  • 3
    Create a begin member function for your stack class that returns an iterator and you wont need template specialization. – a_pradhan Jul 16 '15 at 05:19
  • 2
    Why don't you implement `stack::begin()` and just use the implementation of `std::begin()` as is? – R Sahu Jul 16 '15 at 05:20
  • @RSahu Because the naming conventions of the project use PascalCase for functions. – Emil Laine Jul 16 '15 at 05:23
  • Or write a free `begin` that is found by ADL. You shouldn't really be writing `std::begin(stuff)` anyway. – T.C. Jul 16 '15 at 05:23
  • @AkashPradhan I'd prefer to specialize `std::begin` because [see my previous comment]. – Emil Laine Jul 16 '15 at 05:24
  • @T.C. That too would be against the project's naming conventions. Why not `std::begin(stuff)`? – Emil Laine Jul 16 '15 at 05:26
  • Because it's not how customization points are supposed to be used, and will not work with range-based `for`, for instance; and because it's impossible; specializing a function template can't change the return type, and the one you want to specialize returns "whatever member `begin()` returns", which obviously won't work if you don't have a member `begin()`. – T.C. Jul 16 '15 at 05:34
  • 1
    @zenith, So that you can find other `begin` functions through ADL. It's messy and Eric Niebler proposed a solution to make `std::begin` (and other customization points) do all that so that we don't end up having a repetitive bunch of using statements at the top of functions that grows larger as the list of customization points grows. – chris Jul 16 '15 at 05:34
  • 3
    I would think that the standard takes priority over coding conventions... You should double-check about writing a member begin function. – Marc Glisse Jul 16 '15 at 05:34
  • @T.C. According to [this answer](http://stackoverflow.com/a/8165154/3425536), you can specialize `std::begin()` to make it work with range-based `for`. Oh and I didn't know that specializations can't change return types, thanks. – Emil Laine Jul 16 '15 at 05:38
  • That's a defect changed by [CWG 1442](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1442). – T.C. Jul 16 '15 at 05:45

3 Answers3

17

I'm trying to specialize std::begin for a custom container. I'm doing this because I want to use range-based for with the container.

You are barking up the wrong tree. Range-based for does not use std::begin at all. For class types, the compiler looks up member begin and end directly, and if neither is found, does an ADL-only lookup for free begin and end in the associated namespaces. Ordinary unqualified lookup is not performed; there's no way for std::begin to be picked up if your class is not in the std namespace.

Even if the specialization you want to do is possible (it isn't unless you introduce a member begin() - an explicit specialization for a function template can't change the return type, and the overload at issue returns "whatever member begin() returns"; and if you do introduce a member begin(), why are you specializing std::begin to do what it would have done anyway?), you still won't be able to use it with a range-based for.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • Then what does [this (accepted) answer](http://stackoverflow.com/a/8165154/3425536) mean when it says that range-based `for` compatibility can be achieved by specializing `std::begin`? Is it just wrong? – Emil Laine Jul 16 '15 at 05:50
  • @zenith It's outdated. – T.C. Jul 16 '15 at 05:57
2

Leaving aside the policy and semantic issues of whether you should specialize a function template from the std namespace,

The following snippet does not work:

class stackiterator {};
struct stack { stackiterator Begin() { return stackiterator{};} };

#include <iterator>

namespace std
{
   template <> stackiterator begin<stack>(stack& S)
   {
      return S.Begin();
   }
}

However, the following snippet works just fine:

class stackiterator {};
struct stack { stackiterator begin() { return stackiterator{};} };

#include <iterator>

namespace std
{
   template <> stackiterator begin<stack>(stack& S)
   {
      return S.begin();
   }
}

The key difference is the presence of Begin() vs begin() as a member function of stack. std::begin() is defined as:

template <class C> auto begin(C& c) -> decltype(c.begin());
template <class C> auto begin(const C& c) -> decltype(c.begin());

When you specialize a function template, you must still keep the return type the same. When you don't have begin() as a member of Stack, the compiler does not know how to determine the return type.

That is the reason for error produced by the compiler.

BTW, there is another SO post that partially answers what can be specialized and what can't be specialized.

Looking at the part of the standard that deals with std::begin(), Section 24.3, I don't see anything about not being able to specialize std::begin().

Community
  • 1
  • 1
R Sahu
  • 204,454
  • 14
  • 159
  • 270
1

The right way to add a free function begin that enables for(:) loops is to add, in the namespace of stack, a begin(stack&) and begin(stack const&) function that returns a iterator and const_iterator respectively (and ditto for end)

The other way is to add a member begin() and end() to stack.

Specializing std::begin is bad practice for a number of reasons, not the least of which is that not all for(:) loops will work with it (the lookup rules where changed in the resolution of this defect report). Overloading std::begin is undefined behavior (you may not overload functions in namespace std under the standard: doing so makes your program ill-formed).

This is how it has to be done, even if it violates the naming convention of your project.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 2
    Note that it was supposed to be a specialization of `std::begin` (not an overload) since those are allowed by the standard. – Emil Laine Jul 16 '15 at 14:30