Search code examples
c++c++11stlusing-directivesargument-dependent-lookup

Invoking begin and end via using-directive?


The established idiom for invoking swap is:

using std::swap
swap(foo, bar);

This way, swap can be overloaded for user-defined types outside of the std namespace.

Should we invoke begin and end in the same fashion?

using std::begin;
using std::end;
some_algorithm(begin(some_container), end(some_container));

Or should we just write:

some_algorithm(std::begin(some_container), std::end(some_container));

Solution

  • Using a using-declaration like that is the correct way IMO. It's also what the standard does with the range for loop: if there is no begin or end members present then it will call begin(x) and end(x) with std as an associated namespace (i.e. it will find std::begin and std::end if ADL doesn't find non-member begin and end).

    If you find that writing using std::begin; using std::end; all the time is tedious then you can use the adl_begin and adl_end functions below:

    namespace aux {
    
    using std::begin;
    using std::end;
    
    template<class T>
    auto adl_begin(T&& x) -> decltype(begin(std::forward<T>(x)));
    
    template<class T>
    auto adl_end(T&& x) -> decltype(end(std::forward<T>(x)));
    
    template<class T>
    constexpr bool is_array()
    {
        using type = typename std::remove_reference<T>::type;
        return std::is_array<type>::value;
    }
    
    } // namespace aux
    
    template<class T,
             class = typename std::enable_if<!aux::is_array<T>()>::type>
    auto adl_begin(T&& x) -> decltype(aux::adl_begin(std::forward<T>(x)))
    {
        using std::begin;
        return begin(std::forward<T>(x));
    }
    
    template<class T,
             class = typename std::enable_if<!aux::is_array<T>()>::type>
    auto adl_end(T&& x) -> decltype(aux::adl_end(std::forward<T>(x)))
    {
        using std::end;
        return end(std::forward<T>(x));
    }
    
    template<typename T, std::size_t N>
    T* adl_begin(T (&x)[N])
    {
        return std::begin(x);
    }
    
    template<typename T, std::size_t N>
    T* adl_end(T (&x)[N])
    {
        return std::end(x);
    }
    

    This code is pretty monstrous. Hopefully with C++14 this can become less arcane:

    template<typename T>
    concept bool Not_array()
    {
        using type = std::remove_reference_t<T>;
        return !std::is_array<type>::value;
    }
    
    decltype(auto) adl_begin(Not_array&& x)
    {
        using std::begin;
        return begin(std::forward<Not_array>(x));
    }
    
    decltype(auto) adl_end(Not_array&& x)
    {
        using std::end;
        return end(std::forward<Not_array>(x));
    }
    
    template<typename T, std::size_t N>
    T* adl_begin(T (&x)[N])
    {
        return std::begin(x);
    }
    
    template<typename T, std::size_t N>
    T* adl_end(T (&x)[N])
    {
        return std::end(x);
    }