Improved indices trick
You can get rid of the &lt;type_traits&gt; header by replacing your std::enable_if by a bool template specialization:

template &lt;::std::size_t, ::std::size_t, bool&gt;
struct expand_indices_impl;

template &lt;::std::size_t A, ::std::size_t B&gt;
struct expand_indices:
    expand_indices_impl&lt;A, B, A==B&gt;
{};

template &lt;::std::size_t A, ::std::size_t B&gt;
struct expand_indices_impl&lt;A, B, true&gt;
{
    using indices_type = indices&lt;A&gt;;
};

template &lt;::std::size_t A, ::std::size_t B&gt;
struct expand_indices_impl&lt;A, B, false&gt;
{
    static_assert(A &lt; B, "A &gt; B");
    using indices_type = typename catenate_indices&lt;
        typename expand_indices&lt;A, (A + B) / 2&gt;::indices_type,
        typename expand_indices&lt;(A + B) / 2 + 1, B&gt;::indices_type
    &gt;::indices_type;
};




Moreover, in C++ (and many other programming languages, see Python range for example), ranges tend to have the end-exclusive form [begin, end); therefore, make_indices_range&lt;3, 9&gt; should contain the indices from 3 to 8, and not from 3 to 9. It is easy to alter the behaviour:

template &lt;::std::size_t A&gt;
struct make_indices:
    detail::expand_indices&lt;0, A-1&gt;::indices_type
{};

template &lt;::std::size_t A, ::std::size_t B&gt;
struct make_indices_range:
    detail::expand_indices&lt;A, B-1&gt;::indices_type
{};


As proposed in the comments, make_indices_range also needs to provide an empty range when A == B:

template &lt;::std::size_t A&gt;
struct make_indices_range&lt;A, A&gt;:
    indices&lt;&gt;
{};