Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wrap pattern std::begin; return begin(c); into a function

Is there a way to wrap the pattern into a general, template function?

template <typename C>
auto Begin(C&& c) -> ??? {
  using std::begin;
  return begin(std::forward<C>(c));
}

The question here is how to write the return type of the function here?

The reason I want this is I want to write a template variable

template <typename C>
constexpr bool IsBidirectionalContainer = 
  std::is_base_of<std::bidirectional_iterator_tag,
                  typename std::iterator_traits<
                      decltype(std::begin(std::declval<C>()))>::iterator_category>::value;

The problem here is the std::begin won't find custom overloads of begin for C via ADL. If anyone has workaround for this, it is welcome as well.

like image 224
Kan Li Avatar asked Dec 30 '17 00:12

Kan Li


2 Answers

You need to wrap it inside another namespace, i.e:

namespace details {
    using std::begin;

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

Then:

template <typename C>
constexpr bool IsBidirectionalContainer = 
  std::is_base_of<std::bidirectional_iterator_tag,
                  typename std::iterator_traits<
                      decltype(details::Begin(std::declval<C>()))>::iterator_category>::value;

If for some reason you refuse to define Begin inside a namespace, you can just use a type alias to get around it.

namespace details {
     using std::begin;

     template <typename C>
     using type = decltype(begin(std::forward<C>(c)));
}

template <typename C>
auto Begin(C&& c) -> details::type<C>
{
     return begin(std::forward<C>(c));
}

Although that's probably more work than necessary. A forward declaration is probably sufficient.

like image 76
user9154776 Avatar answered Oct 17 '22 11:10

user9154776


If you’re going to wrap it, I think “why not use the latest best ideas?” Specifically, Eric Niebler makes an argument that these should be function-call objects, not functions!

Here is my fresh version using a C++17 compiler

// ===================
// would be in a reusable header

namespace twostep {

    using std::begin;
    using std::end;

    inline auto Begin = [](auto&& r) -> decltype(begin(std::forward<decltype(r)>(r))) {
        return begin(std::forward<decltype(r)>(r));
    };

    inline auto End = [](auto&& r) -> decltype(end(std::forward<decltype(r)>(r))) {
        return end(std::forward<decltype(r)>(r));
    };

}

using twostep::Begin;
using twostep::End;

Now since this is not replacing the implementation of the std::begin/end functions, I don’t get all the benefit that Eric points out. But, in addition to acting as a wrapper that does the two-step for me, as a normal template function implementation can do, it is itself immune from ADL.

If I refer to twostep::Begin with qualification, it doesn’t matter. But if I import those into my own scope as this listing does to global scope, than an unqualified call to Begin(r) will for-sure see the one that I have made visible at this point, and not find functions named Begin in different namespaces because of all the types involved in r.

like image 31
JDługosz Avatar answered Oct 17 '22 12:10

JDługosz