Roel
Roel

Reputation: 19612

Range based for loop - why are these begin/end functions not found?

Consider:

#include <iostream>
#include <vector>

class A
{
public:
    typedef bool TAll;
    static TAll All;

    typedef std::vector<int> TVec;
    static TVec m_sVec;

    static TVec::iterator begin() { return m_sVec.begin(); }
    static TVec::iterator end() { return m_sVec.end(); }
};

A::TVec A::m_sVec;
A::TAll A::All;

A::TVec::iterator begin(A::TAll& all) { return A::begin(); }
A::TVec::iterator end(A::TAll& all) { return A::end(); }

int _tmain(int argc, _TCHAR* argv[])
{
    A::m_sVec.push_back(1);
    A::m_sVec.push_back(2);
    A::m_sVec.push_back(3);

    for (auto a : A::All) {
    //for (auto a = begin(A::All); a != end(A::All); a++) {
        std::cout << a << std::endl;
    }

    return 0;
}

The version with the range based for loop (so this code as-is) gives me the following error in MSVC2013:

1><snip>: error C3312: no callable 'begin' function found for type 'A::TAll'
1><snip>: error C3312: no callable 'end' function found for type 'A::TAll'

GCC (4.8.3) says (last two lines):

/usr/include/c++/4.8.3/initializer_list:99:5: note: template<class _Tp> constexpr cons
t _Tp* std::end(std::initializer_list<_Tp>)                                           
     end(initializer_list<_Tp> __ils) noexcept                                        
     ^                                                                                
/usr/include/c++/4.8.3/initializer_list:99:5: note:   template argument deduction/subs
titution failed:                                                                      
main.cpp:31:18: note:   mismatched types 'std::initializer_list<_Tp>' and 'bool'      
  for (int a : A::All) { 

The 'normal' for loop that uses the functions (the one that is commented out) works (well, after dereferencing 'a' inside the loop of course); it is my understanding of the standard and the Stroustroup that they should be equivalent. But I guess not. So what is the problem here? Thanks.

Upvotes: 3

Views: 6473

Answers (2)

Roel
Roel

Reputation: 19612

For those above wondering what the point is, consider the following slightly less abstract example:

#include <iostream>
#include <vector>
#include <string>
#include <xfunctional>

#include <boost/iterator/filter_iterator.hpp>

struct Employee
{
    std::string name;
    int age;
    operator int() { return age; }
};

class Employees
{
public:
    struct TOlderThan {
        int m_MinimumAge;
        TOlderThan& operator()(int age)
        {
            m_MinimumAge = age;
            return *this;
        }
    };
    static TOlderThan OlderThan;

    typedef std::vector<Employee> TEmployees;
    static TEmployees sEmployees;

    static TEmployees::iterator begin() { return sEmployees.begin(); }
    static TEmployees::iterator end() { return sEmployees.end(); }
};

Employees::TEmployees Employees::sEmployees;
Employees::TOlderThan Employees::OlderThan;

typedef boost::filter_iterator<std::binder1st<std::less<int>>, Employees::TEmployees::iterator> TFilter;
TFilter begin(const Employees::TOlderThan& min_age) { return boost::make_filter_iterator(std::bind1st(std::less<int>(), min_age.m_MinimumAge), Employees::begin(), Employees::end()); }
TFilter end(const Employees::TOlderThan& min_age) { return boost::make_filter_iterator(std::bind1st(std::less<int>(), min_age.m_MinimumAge), Employees::end(), Employees::end()); }

int main(int argc, _char* argv[])
{
    Employees::sEmployees.push_back({"John", 34});
    Employees::sEmployees.push_back({"Pete", 48});
    Employees::sEmployees.push_back({"Jake", 59});

    for (Employee& e : Employees::OlderThan(40)) {
        std::cout << e.name << std::endl;
    }

    return 0;
}

Outputs, as expected

Pete
Jake

Basically, this feature lets you build almost DSL-style behaviour into your API's with less than 10 lines of code. Pretty cool.

Upvotes: 1

Casey
Casey

Reputation: 42554

Per C++11 [stmt.ranged]/1, your loop:

for (auto a : A::All) {
    std::cout << a << std::endl;
}

is equivalent to:

{
    auto && __range = (A::All);
    for ( auto __begin = begin-expr,
               __end = end-expr;
          __begin != __end;
          ++__begin ) {
        auto a = *__begin;
        {
            std::cout << a << std::endl;
        }
    }
}

where the determination of the expressions begin-expr and end-expr depends on the type _RangeT of the initializer expression A::All (bool in this case):

  • if _RangeT is an array type, ...
  • if _RangeT is a class type, ...
  • otherwise, begin-expr and end-expr are begin(__range) and end(__range), respectively, where begin and end are looked up with argument-dependent lookup (3.4.2) [emphasis added]. For the purposes of this name lookup, namespace std is an associated namespace.

Since bool is neither an array or class type, the third bullet applies; the expressions are begin(__range) and end(__range), but begin and end are resolved using ADL with std as an associated namespace. Per 3.4.2 [basic.lookup.argdep]/2:

For each argument type T in the function call, there is a set of zero or more associated namespaces and a set of zero or more associated classes to be considered. The sets of namespaces and classes is determined entirely by the types of the function arguments (and the namespace of any template template argument). Typedef names and using-declarations used to specify the types do not contribute to this set. [emphasis added] The sets of namespaces and classes are determined in the following way:

  • If T is a fundamental type, its associated sets of namespaces and classes are both empty.
  • ...

So begin and end are looked up only in the std namespace, where several declarations are found, but none that can accept an argument of type bool. The program is ill-formed.

Upvotes: 7

Related Questions