3

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.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
Roel
  • 19,338
  • 6
  • 61
  • 90
  • 1
    Excuse my ignorance but what `auto a : A::All` means, when `A::All` is of type bool? – 101010 Jan 13 '15 at 23:24
  • @quantdev got me first, you can't iterate over a `bool`. – 101010 Jan 13 '15 at 23:27
  • or `for(auto&& a : A())` Anyway, are you porting an ancient app, or why do you use `TCHAR` ([Is TCHAR still relevant?](http://stackoverflow.com/a/3002494))? – Deduplicator Jan 13 '15 at 23:28
  • TCHAR thing is default-inserted by MSVC wizard for my test app; substitute _tmain with main and TCHAR with char for 'standard' version. What I want is to use the static begin/end iterators of A. I want to use A:All to trick the compiler into calling the free-standing begin/end functions, which then call the static begin/end. My interpretation of the standard is that the compiler should look up two functions begin and end that take the type of the expression (in this case, A:All) and then call those functions. TAll could be a typedef for anything, or an empty struct, I just chose bool. – Roel Jan 13 '15 at 23:35
  • And no, I don't want to iterate over A::m_sVec, I would have written so if I wanted to - this is a minimal sample that shows my question about the syntax and the lookup rule for free-standing begin/end functions in a range for loop. In my real case, m_sVec is a private member that should be accessible through a filtering iterator only. – Roel Jan 13 '15 at 23:39
  • Oh and also, I can't do for (auto a : A()) because A's constructor is private. There are a fixed number of A's in my code that are instantiated by a factory, so I cannot create new ones just to access the static iterators. – Roel Jan 13 '15 at 23:43
  • 1
    `begin(A::TAll& all)` will never be found as a free function since range-base for loops use pure ADL to find `begin` and `end`; and no namespace is associated with the type `bool`. – dyp Jan 13 '15 at 23:56
  • There are several workarounds, for example: wrap the `bool` in a custom `struct`. ADL will then find free functions in namespaces associated with that `struct`. That wrapper could be a nested class. – dyp Jan 13 '15 at 23:59
  • 1
    I'm not sure if that `bool` is supposed to convey some state. If it does not, you can just use a tag type instead of a wrapper: http://coliru.stacked-crooked.com/a/b87730d739a5cda6 – dyp Jan 14 '15 at 00:03
  • Is there any particular reason that you have such a complicated example? Isn't this equivalent: https://gist.github.com/sharth/bfeab602f1c7273cd0c2 – Bill Lynch Jan 14 '15 at 06:07
  • @dyp Thanks, indeed a struct works. – Roel Jan 14 '15 at 09:15
  • @Bill Yes in hindsight (for me), they are equivalent. I didn't know what the problem was when I asked my question so I started from my real code and took away templates and indirections and other stuff until I had what I thought was the shortest form of my problem that still showed what I was trying to do. But yeah, now that I know what the issue was, your is really the shortest that still shows the issue. – Roel Jan 14 '15 at 09:17

2 Answers2

7

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.

Casey
  • 41,449
  • 7
  • 95
  • 125
  • Great, thank you, I hadn't realized that typedefs are treated differently for the purposes of Koenig lookup. And indeed there are two ways of getting the code to do what I want: either put begin()/end() in the std:: namespace, or make my TAll something that is not a typedef or fundamental type (so, a struct, basically). And when I do keep All a bool, I can make it work by not typedef'ing it, *and* putting begin()/end() in std::. This is quite a powerful feature - it'll let you do stuff like for (Employee& e : Employees::OlderThan(40)) {...} without needing all that much boilerplate. – Roel Jan 14 '15 at 09:39
  • This answer really helped me! Saved me hours of frustration! – Marco Merlini Oct 26 '18 at 18:18
1

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.

Roel
  • 19,338
  • 6
  • 61
  • 90