12
votes

I'm trying to create a function that takes an underlying container, and returns a boost::iterator_range based on a custom iterator that does some processing on the elements.

E.g.

// The range class, templated on the underlying iterator type
template<class Iter> using CustomRange = boost::iterator_range<CustomIterator<Iter>>;

using std::begin;
template <class Container>
auto make_custom_range(Container& c) -> CustomRange<decltype(begin(c))> {
    using std::end;
    return make_custom_range_from_iterators(begin(c),end(c));
}

The code works (given suitable definitions for CustomIterator and make_custom_range_from_iterators).

My concern is the using std::begin declaration, which I think will cause std::begin to be imported into the entire namespace where my function is declared. I prefer not to use std::begin explicitly in the decltype so that ADL can work (as in this question: Relying on ADL for std::begin() and std::end()?).

I think in C++14, I could just use an auto return type here. Is there a C++11 solution? Is there a way to let the return type see the using declaration without exposing it to the entire namespace?

2
ADL should make it that the usings are not required - Creris
using is bound by scope. - Captain Obvlious
@Creris It's required if you want C-style arrays to also work - Shoe
Throw the deduction facility into a details namespace. - T.C.
@Creris Yes, but without those usings, no begin and end functions will be found using ADL for C-style arrays. - Shoe

2 Answers

9
votes

Put the using declaration in a separate namespace:

namespace adl_helper
{
    using std::begin;

    template <typename T>
    auto adl_begin(T&& t) -> decltype(begin(std::forward<T>(t)));  
}

template <class Container>
auto make_custom_range(Container& c)
    -> CustomRange<decltype(adl_helper::adl_begin(c))>
//                          ~~~~~~~~~~~~~~~~~~~~^
{
    using std::begin;
    using std::end;
    return make_custom_range_from_iterators(begin(c),end(c));
}

DEMO

3
votes

Throw everything into another namespace and put your usings in there. Then bring your new helpers into your top namespace:

namespace details {
    using std::begin;
    using std::end;

    template <typename C>
    auto adl_begin(C&& c) -> decltype(begin(std::forward<C>(c))) {
        return begin(std::forward<C>(c));
    }

    template <typename C>
    auto adl_end(C&& c) -> decltype(end(std::forward<C>(c))) {
        return end(std::forward<C>(c));
    }
}

using details::adl_begin;
using details::adl_end;

template <typename C>
using adl_begin_t = decltype(adl_begin(std::declval<C>()));

template <typename C>
using adl_end_t = decltype(adl_end(std::declval<C>()));

In C++14, you won't need the trailing return types, but will also need to do the same for cbegin and cend. With that, you don't have to remember to have the usings ever again and just use the adl_* methods everywhere:

template <class Container>
CustomRange<adl_begin_t<Container&>> make_custom_range(Container& c) {
    return make_custom_range_from_iterators(adl_begin(c), adl_end(c));
}