7

This is an extension to this question from 2011: Range-based for loops and ADL

Using Visual Studio 2015, I'm not able to make a range-based for loop for a custom container using Argument Dependent Lookup (ADL).

I have made a very simple test case below with a custom container:

#include <vector>

namespace Foo
{
    template <typename T>
    class Container
    {
    public:

        std::vector<T> values;
    };
}

template <typename T>
typename std::vector<T>::iterator begin(Foo::Container<T>& foo)
{
    return foo.values.begin();
}

template <typename T>
typename std::vector<T>::iterator end(Foo::Container<T>& foo)
{
    return foo.values.end();
}

Using this container and ADL, the following test compiles perfectly fine:

int main(int argc, char* argv[])
{
    Foo::Container<int> values;

    for (auto it = begin(values); it != end(values); ++it)
    {
        ...
    }

    return 0;
}

As it should. I'm not sure if ADL is even being utilized here, but regardless, it makes sense. From MSDN documentation, we have:

Keep in mind these facts about range-based for:

  • Automatically recognizes arrays.

  • Recognizes containers that have .begin() and .end().

  • Uses argument-dependent lookup begin() and end() for anything else.

From what I understand of ADL, and the documentation above, the following should also compile:

int main(int argc, char* argv[])
{
    Foo::Container<int> values;

    for (auto value : values)
    {
        ...
    }

    return 0;
}

But it doesn't. Instead, I get the following errors:

error C3312: no callable 'begin' function found for type 'Foo::Container<int>'
error C3312: no callable 'end' function found for type 'Foo::Container<int>'

So what's going on here? Is my interpretation of ADL incorrect, or is this a bug with MSVC 14.0 Compiler?

Community
  • 1
  • 1
Zeenobit
  • 4,954
  • 3
  • 34
  • 46
  • 1
    IINM `begin()` and `end()` should be in `Foo`. – Quentin Sep 20 '15 at 16:41
  • This is not dependent on ADL since `begin` and `end` are not in the namespace `Foo`, but it seems to work fine in gcc: http://coliru.stacked-crooked.com/a/12047609140d05a9 . If you change `begin` and `end` to take a `const` ref instead, does it work on MSVC? – rici Sep 20 '15 at 16:53
  • @rici, No, it doesn't. It makes me wonder why it works in GCC though if Pavlo's answer is right. Which compiler is the correct one?! :) – Zeenobit Sep 20 '15 at 17:03
  • @Zeenobit In C++11, both are kind of correct since the standard had a defect, and in C++14, your code must be rejected. – Baum mit Augen Sep 20 '15 at 18:51

1 Answers1

8

You have to place both begin and end into Foo namespace for ADL to work. This is because ADL will look into namespaces of corresponding arguments to search definitions of begin and end.

namespace Foo
{
    template <typename T>
    class Container
    {
    public:

        std::vector<T> values;
    };

    template <typename T>
    typename std::vector<T>::iterator begin(Foo::Container<T>& foo)
    {
        return foo.values.begin();
    }

    template <typename T>
    typename std::vector<T>::iterator end(Foo::Container<T>& foo)
    {
        return foo.values.end();
    }
}

UPD: The reason why begin and end from global namespace are not considered is because of the updated standard saying that begin and end are looked up in the associated namespaces but ordinary unqualified lookup is not performed. This is a consequence of bug fix in the standard (http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1442).

Pavlo Mur
  • 350
  • 3
  • 6
  • It should work fine with `begin` and `end` defined in the global namespace, too. Why wouldn't it? – rici Sep 20 '15 at 16:54
  • @rici this is because ordinary unqualified lookup (lookup in global namespace) is not performed according to updated standard: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1442 – Pavlo Mur Sep 20 '15 at 17:11
  • @PavloMur Thanks for the clarification. – Zeenobit Sep 20 '15 at 19:11
  • pavlo: Interesting, thanks. I'd completely missed that change. – rici Sep 20 '15 at 19:17