4

Let's assume I have the following Data class:

struct Data {
   char foo[8];
   char bar;
};

and the following function, my_algorithm, which takes a pair of char * (similar to an STL algorithm):

void my_algorithm(char *first, char *last);

For Data's foo data member, instead of calling my_algorithm() like this:

Data data;
my_algorithm(data.foo, data.foo + 8);

I can use the std::begin() and std::end() convenience function templates:

my_algorithm(std::begin(data.foo), std::end(data.foo));

I would like to achieve something similar to Data's bar data member. That is, instead of writing:

my_algorithm(&data.bar, &data.bar + 1);

I would like to write something like:

my_algorithm(begin(data.bar), end(data.bar));

Therefore, I've defined the two following ordinary (non-template) functions for this case:

char* begin(char& c) { return &c; }
char*   end(char& c) { return &c + 1; }

So that I would be able to write code like the following:

Data data;

using std::begin;
using std::end;

my_algorithm(begin(data.foo), end(data.foo)); // ok - std::begin()/std::end()
my_algorithm(begin(data.bar), end(data.bar)); // Error!!!

With the using declarations above I would have expected std::begin()/std::end() and ::begin()/::end() to be in the same overload set, respectively. Since the functions ::begin() and ::end() are a perfect match for the latter call and they are not templates, I would have expected the last call to my_algorithm() to match them. However, the ordinary functions are not considered at all. As a result the compilation fails, because std::begin() and std::end() are not matches for the call.

Basically, the latter call acts as if I had written instead:

my_algorithm(begin<>(data.bar), end<>(data.bar));

That is, only the function templates (i.e., std::begin()/std::end()) are considered by the overload resolution process, not the ordinary functions (i.e., not ::begin()/::end()).

It only works as expected, if I fully qualify the calls to ::begin()/::end():

my_algorithm(::begin(data.bar), ::end(data.bar));

What am I missing here?

JFMR
  • 23,265
  • 4
  • 52
  • 76
  • 3
    If you want it to act like an array, make it an array :) `char bar[1];`. This doesn't *really* answer your question, but may help your specific case. – Kevin Jun 03 '19 at 17:39
  • 2
    Can't duplicate on my machine or at https://ideone.com/8cTR5r. – R Sahu Jun 03 '19 at 17:41
  • What version are you using of what compiler and what C++ standard library? – Ben Voigt Jun 03 '19 at 17:51
  • 2
    @Justin "`using std::begin; using std::end;` enables ADL" is not correct. ADL is enabled by using an unqualified name. The `using` lets the unqualified name find the members of `namespace std`. If you do this in a local scope, it hides globals, because local declarations always hide declarations in larger scopes. One can write `using std::begin; using ::begin;` to consider both plus names found through ADL. – Ben Voigt Jun 03 '19 at 17:55

2 Answers2

7

Let's get a complete, reproducible example:

#include <iterator>

char* begin(char& c) { return &c; }
char*   end(char& c) { return &c + 1; }

namespace ns {
    void my_algorithm(char *first, char *last);

    void my_function() {
        using std::begin;
        using std::end;

        char c = '0';
        my_algorithm(begin(c), end(c));
    }
}

When you make the unqualified call to begin(c) and end(c), the compiler goes through the process of unqualified name lookup (described on the Argument-dependent lookup page of cppreference).

For regular unqualified name lookup, the process is roughly to start at the namespace you are currently in—::ns in this case—and only move out a namespace if you don't find the specific name.

If a function call is unqualified, as it is here with begin(c) and end(c), argument dependent lookup can occur, which finds free functions declared in the same namespace as the types of the functions' arguments, through the process of extending the overload set by finding "associated namespaces."

In this case, however, char is a fundamental type, so argument dependent lookup doesn't allow us to find the global ::begin and ::end functions.

For arguments of fundamental type, the associated set of namespaces and classes is empty

cppreference: argument dependent lookup

Instead, as we already have using std::begin; using std::end;, the compiler already sees possible functions for begin(...) and end(...)—namely those defined in namespace ::std—without having to move out a namespace from ::ns to ::. Thus, the compiler uses those functions, and compilation fails.


It's worth noting that the using std::begin; using std::end; also block the compiler from finding the custom ::begin and ::end even if you were to place them inside ::ns.


What you can do instead is write your own begin and end:

#include <iterator>

namespace ns {
    char* begin(char& c) { return &c; }
    char*   end(char& c) { return &c + 1; }

    template <typename T>
    auto begin(T&& t) {
        using std::begin;
        // Not unbounded recursion if there's no `std::begin(t)`
        // or ADL `begin(t)`, for the same reason that our
        // char* begin(char& c); overload isn't found with
        // using std::begin; begin(c);
        return begin(t);
    }

    template <typename T>
    auto end(T&& t) {
        using std::end;
        return end(t);
    }

    void my_algorithm(char *first, char *last);

    void my_function() {
        char c = '0';
        my_algorithm(ns::begin(c), ns::end(c));
    }
}
Justin
  • 24,288
  • 12
  • 92
  • 142
  • 2
    I assumed fundamental types included the global namespace but now that I actually read that part, it explicitly doesn't pull in anything : "For arguments of fundamental type, the associated set of namespaces and classes is empty". – François Andrieux Jun 03 '19 at 18:05
  • Great answer! Just for clarification: The `using` directive doesn't block *ADL*, does it? Otherwise, the `using std::swap;` trick for using `std::swap()` as a fallback for `swap()` when there is no such a function in the enclosing namespace of the class of the objects to swap couldn't work, right? – JFMR Jun 03 '19 at 20:15
  • 1
    @ElProfesor No, it doesn't block ADL. – Justin Jun 03 '19 at 20:50
  • 1
    @ElProfesor ADL has no impact. – Vlad Jun 04 '19 at 00:11
  • 2
    So verbose that it's difficult to say what this answer about. Long story about ADL and conclusion that ADL does not matters here. Another answer is clear. Why you need such helpers... –  Jun 04 '19 at 12:52
  • Other (better?) solution 1: `namespace std_iterators { using std::begin; using std::end; }` and inside a function `using namespace std_iterators` instead of `using std::begin; using std::end;` ([link](https://coliru.stacked-crooked.com/a/e4e7d333ffe137e8)). Solution 2: same as before but turn `std_iterators` into an unnamed namespace - `std::begin` and `std::end` would be "globally" available for every function without the need of `using` directive ([link](https://coliru.stacked-crooked.com/a/751209095e510a88)). – adf88 Dec 29 '22 at 13:42
-4

The title of question is "Overloading std::begin()". Overloading is possible only within the same scope. That is you can't overload names from different scopes. In another scope we can only make efforts to help lookup name. Essentially, here "using std::begin" declaration hides ::begin in question's code. See S.Lippman for reference:

functions that are members of two distinct namespaces do not overload one another.

Scope of a using Declaration. Names introduced in a using declaration obey normal scope rules. Entities with the same name defined in an outer scope are hidden.

As soon as parameter is char and char is fundamental type - argument dependent lookup should not be taken into consideration - as mentioned in comments - there is no associated namespace with fundamental types. Again, the question was: "What am I missing?" - therefore the answer is focused only on reasons - recommendations may be too broad.

Vlad
  • 1,977
  • 19
  • 44
  • 4
    No, there's [argument dependent lookup](https://en.cppreference.com/w/cpp/language/adl) which allows several namespaces to be considered for overloads based on the arguments. – François Andrieux Jun 03 '19 at 17:43
  • @François Andrieux You are wrong - I didn't say about lookup. Lippman C++ primer: "each namespace maintains its own scope. As a consequence, functions that are members of two distinct namespaces do not overload one another" – Vlad Jun 03 '19 at 17:55
  • 3
    You're right that the names don't overload each other, but they are both considered during overload resolution. Meaning that the fact that they aren't technically overloads of each other isn't the answer to this question. If it *is* the answer to this question, you'll need to clarify how because I missed it. Edit : From the other answer it seems the free functions at the global namespace doesn't qualify. – François Andrieux Jun 03 '19 at 18:01
  • 1
    TS tries to overload names from std and global namespace - I indicated that this is mistake. Yes, lookup resolution may find name in outer scope - but this is not what TS wants - not overloading. using may lift names in outer namespace - but this is not overloading. – Vlad Jun 03 '19 at 18:05