Dependant non-type template parameter and variadic template
Asked Answered
L

3

3

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.

Lyford answered 22/4, 2014 at 19:52 Comment(7)
Does this not work with g++4.8.1?Oneman
If it doesn't you could try something like this instead.Oneman
"it is not allowed to have a default template parameter after a variadic template" This is possible if you wrap the sequence and use partial specialization (e.g. pass the already constructed sequence as a std::integer_sequence).Oneman
@Oneman If you instantiate it, the first one does not work.Lyford
Ouch yes, I keep forgetting typedefs don't instantiate. The second one works, though (coliru uses a g++4.8.2).Oneman
@Oneman That's some ugly hacks, but it works. Would you turn that into an answer? :)Lyford
There's a solution from 2012 here which also allows a non-unit stride.Humdrum
L
3

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)

Lauritz answered 23/4, 2014 at 5:58 Comment(3)
Hey! You're not Yakk.. ;)Oneman
That does seem to be a cleaner and less hackish solution :)Lyford
And as a result of my experiments with the above code (I screwed up while writing the live example by mixing up index_sequence and integer_sequence), I just submitted Bugzilla #60942 :)Lauritz
O
3

I'm just waiting for Yakk to suddenly appear out of nowhere and provide a non-hacky better solution ;) (I'm too tired to come up with something better..) but alas, this works for g++4.8.1:

#include <type_traits>

// C++1y Standard Library
// use this ONLY FOR TESTING PURPOSES
namespace std
{
    template<class T, T... vals> struct integer_sequence {};
}

namespace details
{
    template<typename T>
    struct minus_helper;
    template<typename T, T val>
    struct minus_helper< std::integral_constant<T, val> >
    {
        using type = std::integral_constant<T, val-1>;
    };

    template<typename T>
    using minus = typename minus_helper<T>::type;

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

    template<typename Int, Int P, Int... N>
    struct increasing_integer_range<Int, std::integral_constant<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<std::size_t S, std::size_t E>
using index_range = integer_range<std::size_t, S, E>;

int main()
{
    index_range<1, 5> f;
}

The reason why I think this could be done better is that it seems the increasing and decreasing range implementations are redundant. But I might have to consider corner cases (overflow) before providing a unifying solution.

For example, you can easily make a shifted integer range by using std::make_integer_range plus partial specialization plus pack expansion. But this might overflow.

Oneman answered 22/4, 2014 at 20:35 Comment(2)
If you think that it could be better, i will wait a little bit before accepting, but that's already good anyway :)Lyford
It'll not get any better today. Maybe tomorrow.Oneman
L
3

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)

Lauritz answered 23/4, 2014 at 5:58 Comment(3)
Hey! You're not Yakk.. ;)Oneman
That does seem to be a cleaner and less hackish solution :)Lyford
And as a result of my experiments with the above code (I screwed up while writing the live example by mixing up index_sequence and integer_sequence), I just submitted Bugzilla #60942 :)Lauritz
R
2

For example, you can easily make a shifted integer range by using std::make_integer_range plus partial specialization plus pack expansion.

A brief demonstration.

template<typename T>
constexpr T abs(T t)
{ return t < static_cast<T>(0) ? -t : t; }

template<typename Sequence>
struct match_sequence {};

template<typename Int, Int... Ns>
struct match_sequence<std::integer_sequence<Int, Ns...>> {
    template<Int Base, Int Stride = static_cast<Int>(1)>
    using Offset = std::integer_sequence<Int, Base + Stride * Ns...>;
};

template<typename Int, Int Low, Int High>
struct integer_range {
private:
    static constexpr auto width = abs(High - Low);
    using base = std::make_integer_sequence<Int, width>;
    static constexpr bool increasing = High > Low;
    static constexpr auto stride = increasing ? static_cast<Int>(1) : static_cast<Int>(-1);

public:
    using type = typename match_sequence<base>::template Offset<Low, stride>;
};
Rida answered 22/4, 2014 at 22:39 Comment(3)
With that fix, it compiles (though my hacky make_integer_sequence is rejected by g++ for exactly the same issues Morwenn has in the OP - but that's not the issue here I guess).Oneman
That's interesting. But currently, it has one problem: integer_range does not inherit from/alias to std::integer_sequence. We only need one more alias template to solve the problem though.Lyford
@Oneman By the way, compiler bug or not?Lyford

© 2022 - 2024 — McMap. All rights reserved.