Search code examples
c++templatestemplate-specializationc++14indices

Dependant non-type template parameter and variadic template


I am trying to extend the possibilities offered by std::integer_sequence with a new class named integer_range (which obiously creates a sequence of integers between two bounds). My implementation was based of my answer to this question that I tried to adapt to std::integer_sequence:

namespace details
{
    template<typename Int, Int C, Int P, Int... N>
    struct increasing_integer_range:
        increasing_integer_range<Int, C-1, P+1, N..., P>
    {};

    template<typename Int, Int C, Int P, Int... N>
    struct decreasing_integer_range:
        decreasing_integer_range<Int, C+1, P-1, N..., P>
    {};

    template<typename Int, Int P, Int... N>
    struct increasing_integer_range<Int, 0, P, N...>:
        std::integer_sequence<Int, N...>
    {};

    template<typename Int, Int P, Int... N>
    struct decreasing_integer_range<Int, 0, P, N...>:
        std::integer_sequence<Int, N...>
    {};
}

template<typename Int, Int S, Int E,  bool Increasing=(S<E)>
struct integer_range;

template<typename Int, Int S, Int E>
struct integer_range<Int, S, E, true>:
    details::increasing_integer_range<Int, std::integral_constant<Int, E-S>, S>
{};

template<typename Int, Int S, Int E>
struct integer_range<Int, S, E, false>:
    details::decreasing_integer_range<Int, std::integral_constant<Int, E-S>, S>
{};

template<std::size_t S, std::size_t E>
using index_range = integer_range<std::size_t, S, E>;

I thought that the change would be trivial (adding a typename template parameter), but this actually introduces a problem with a dependant non-type parameter in the specialization for 0. Here is the compiler error:

error: type `Int` of template argument `0` depends on a template parameter

The basic problem already has some solutions. However, since I use variadic templates, it becomes even harder to fix: this answer can't work because I it is not allowed to have a default template parameter after a variadic template. Therefore, I tried to implement the fix mentioned in the accepted answer but it seems that, as mentioned in the comments, my compiler (g++ 4.8.1) is unable to disambiguate and considers that the two following specialization are both equally specialized:

  • struct increasing_integer_range<Int, std::integral_constant<Int, C>, P, N...>: /* */
  • struct increasing_integer_range<Int, std::integral_constant<Int, 0>, P, N...>: /* */

Is there any other way to fix this problem? I am out of ideas.


Solution

  • I would simply reduce your integer_range to a single, non-recursive call to std::integer_sequence:

    namespace details
    {
        template<typename Int, typename, Int S>
        struct increasing_integer_range;
    
        template<typename Int, Int... N, Int S>
        struct increasing_integer_range<Int, std::integer_sequence<Int, N...>, S>
            : std::integer_sequence<Int, N+S...>
        {};
    
        template<typename Int, typename, Int S>
        struct decreasing_integer_range;
    
        template<typename Int, Int... N, Int S>
        struct decreasing_integer_range<Int, std::integer_sequence<Int, N...>, S>
            : std::integer_sequence<Int, S-N...>
        {};
    }
    
    template<typename Int, Int S, Int E,  bool Increasing=(S<E)>
    struct integer_range;
    
    template<typename Int, Int S, Int E>
    struct integer_range<Int, S, E, true>:
        details::increasing_integer_range<Int, std::make_integer_sequence<Int, E-S>, S>
    {};
    
    template<typename Int, Int S, Int E>
    struct integer_range<Int, S, E, false>:
        details::decreasing_integer_range<Int, std::make_integer_sequence<Int, S-E>, S>
    {};
    
    template<std::size_t S, std::size_t E>
    using index_range = integer_range<std::size_t, S, E>;
    

    Which I tested with:

    template<std::size_t... N>
    void dummy( const std::integer_sequence< std::size_t, N... >& );
    
    int main()
    {
        dummy( index_range< 2, 5 >() );
        dummy( index_range< 5, 2 >() );
    }
    

    getting the expected linker errors:

    main.cpp:(.text.startup+0xa): undefined reference to `void dummy<2ul, 3ul, 4ul>(detail::integer_sequence<unsigned long, 2ul, 3ul, 4ul> const&)'
    main.cpp:(.text.startup+0x14): undefined reference to `void dummy<5ul, 4ul, 3ul>(detail::integer_sequence<unsigned long, 5ul, 4ul, 3ul> const&)'
    

    Live example (with own implementation of integer_sequence, just skip the first part)