How it works...

These non-member functions were introduced in different versions of the standard, but all of them were modified in C++17 to return constexpr auto:

  • std::begin() and std::end() in C++11
  • std::cbegin()/std::cend(), std::rbegin()/std::rend(), and std::crbegin()/std::crend() in C++14
  • std::data(), std::size(), and std::empty() in C++17

The begin()/end() family of functions have overloads for container classes and arrays, and all they do is the following:

  • Return the results of calling the container-corresponding member function for containers.
  • Return a pointer to the first or one-past-last element of the array for arrays.

The actual typical implementation for std::begin()/std::end() is the following:

    template<class C>
constexpr auto inline begin(C& c) -> decltype(c.begin())
{
return c.begin();
}
    template<class C>
constexpr auto inline end(C& c) -> decltype(c.end())
{
return c.end();
}

template<class T, std::size_t N>
constexpr T* inline begin(T (&array)[N])
{
return array;
}

template<class T, std::size_t N>
constexpr T* inline begin(T (&array)[N])
{
return array+N;
}

Custom specialization can be provided for containers that do not have corresponding begin()/end() members but can still be iterated. The standard library actually provides such specializations for std::initializer_list and std::valarray.

Specializations must be defined in the same namespace where the original class or function template has been defined. Therefore, if you want to specialize any of the std::begin()/std::end() pairs you must do it in the std namespace.

The other non-member functions for container access, that were introduced in C++17, have also several overloads:

  • std::data() has several overloads; for a class C it returns c.data(), for arrays it returns the array, and for std::initializer_list<T> it returns the il.begin().
        template <class C> 
constexpr auto data(C& c) -> decltype(c.data())
{
return c.data();
}

template <class C>
constexpr auto data(const C& c) -> decltype(c.data())
{
return c.data();
}

template <class T, std::size_t N>
constexpr T* data(T (&array)[N]) noexcept
{
return array;
}

template <class E>
constexpr const E* data(std::initializer_list<E> il) noexcept
{
return il.begin();
}
  • std::size() has two overloads; for a class C it returns c.size(), and for arrays it returns the size N.
        template <class C> 
constexpr auto size(const C& c) -> decltype(c.size())
{
return c.size();
}

template <class T, std::size_t N>
constexpr std::size_t size(const T (&array)[N]) noexcept
{
return N;
}
  • std::empty() has several overloads; for a class C it returns c.empty(), for arrays it returns false, and for std::initializer_list<T> it returns il.size() == 0.
        template <class C> 
constexpr auto empty(const C& c) -> decltype(c.empty())
{
return c.empty();
}

template <class T, std::size_t N>
constexpr bool empty(const T (&array)[N]) noexcept
{
return false;
}

        template <class E> 
constexpr bool empty(std::initializer_list<E> il) noexcept
{
return il.size() == 0;
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset