Partial specialization of variadic value template

Viewed 92

If I have a value templated class

template<int... values>
class A {};

Can I specialize it "easely" (without template recursion) for all sequences with a undetermined-length sequence of trailing 0's ?

// Pseudo code
template<int... values>
class A<values..., 0...> {};

Examples that should use the specialized version (in fact that should match every sequence eventually with zero trailing 0's):

A<1, 0>{}; // use overloaded version with overloaded template values = <1>
A<1, 1>{}; // use with values=<1, 1>
A<1, 2, 3, 0, 0, 0>{}; // use with template values = <1, 2, 3>
A<1, 2, 3>{}; // use with values=<1, 2, 3>
3 Answers

You can use constexpr functions to strip the trailing zeros from the end of the parameter pack:

template<int... values>
class A_impl {};

template<typename Arr>
constexpr auto num_trailing_zeros(Arr const& arr) {
    int count = 0;

    for (int i = arr.size() - 1; i >= 0; --i) {
        if (arr[i] != 0) return count;
        ++count;
    }

    return count;
}

// Not `constexpr` because we aren't providing a definition;
// we only care about the type.
// Marking it `constexpr` produces compilation warnings.
template<auto const& arr, std::size_t... Is>
auto A_from_arr(std::index_sequence<Is...>) -> A_impl<arr[Is]...>;

// Not `constexpr` because we need the `static constexpr` `arr` variable so
// that we can pass it as a reference argument.
// `static`s aren't allowed inside `constexpr` functions.
// This is fine, because we only care about the type.
template<int... values>
auto A_without_trailing_zeros() {
    static constexpr auto arr = std::array { values... };
    constexpr auto num_trailing = num_trailing_zeros(arr);
    return A_from_arr<arr>(std::make_index_sequence<sizeof...(values) - num_trailing>{});
}

template<int... values>
using A = decltype(A_without_trailing_zeros<values...>());

Demo

Not sure about what do you exactly want... and surely you can't obtain what do you want in a simple way with a simple class A... but if you accept to add a level of indirection... I mean: a struct/class A<1, 2, 3, 0, 0, 0> that inherit from B<1, 2, 3>...

We need something that strip the trailing zero from an integer sequence.

Maybe there are simpler methods but I imagine a custom type traits as follows

template <typename, typename, int...>
struct szh;

// non zero element case    
template <int ... As, int ... Bs, int C, int ... Ds>
struct szh<std::integer_sequence<int, As...>,
           std::integer_sequence<int, Bs...>,
           C, Ds...>
 : public szh<std::integer_sequence<int, As..., Bs..., C>,
              std::integer_sequence<int>,
              Ds...>
 { };

// zero element case
template <int ... As, int ... Bs, int ... Ds>
struct szh<std::integer_sequence<int, As...>,
           std::integer_sequence<int, Bs...>,
           0, Ds...>
 : public szh<std::integer_sequence<int, As...>,
              std::integer_sequence<int, Bs..., 0>,
              Ds...>
 { };

// ground case
template <int ... As, int ... Bs>
struct szh<std::integer_sequence<int, As...>,
           std::integer_sequence<int, Bs...>>
 { using type = std::integer_sequence<int, As...>; };

template <int ... Is>
using strip_trailing_zeros
   = typename szh<std::integer_sequence<int>,
                  std::integer_sequence<int>,
                  Is...>::type;

I've made it generic because I think it's better make complicated code reusable. But if the final type is B<As...> instead of std::integer_sequence<int, As...>, the following code can be simplified a little but strip_trailing_zeros can't be re-used.

Now a simple class B (observe that the constructor prints the Is...)

template <int ... Is>
struct B
 { B () { ((std::cout << Is), ...); std::cout << '\n'; } };

and a converter (only declared) from std::integer_sequenc<int, Is...> to B<int...>

template <int ... Is>
B<Is...> foo (std::integer_sequence<int, Is...>);

so A become

template <int ... Is>
struct A : public decltype(foo(strip_trailing_zeros<Is...>{}))
 { };

The following is a full compiling example

#include <iostream>
#include <utility>

template <typename, typename, int...>
struct szh;

template <int ... As, int ... Bs, int C, int ... Ds>
struct szh<std::integer_sequence<int, As...>,
           std::integer_sequence<int, Bs...>,
           C, Ds...>
 : public szh<std::integer_sequence<int, As..., Bs..., C>,
              std::integer_sequence<int>,
              Ds...>
 { };

template <int ... As, int ... Bs, int ... Ds>
struct szh<std::integer_sequence<int, As...>,
           std::integer_sequence<int, Bs...>,
           0, Ds...>
 : public szh<std::integer_sequence<int, As...>,
              std::integer_sequence<int, Bs..., 0>,
              Ds...>
 { };

template <int ... As, int ... Bs>
struct szh<std::integer_sequence<int, As...>,
           std::integer_sequence<int, Bs...>>
 { using type = std::integer_sequence<int, As...>; };

template <int ... Is>
using strip_trailing_zeros
   = typename szh<std::integer_sequence<int>,
                  std::integer_sequence<int>,
                  Is...>::type;

template <int ... Is>
struct B
 { B () { ((std::cout << Is), ...); std::cout << '\n'; } };

template <int ... Is>
B<Is...> foo (std::integer_sequence<int, Is...>);

template <int ... Is>
struct A : public decltype(foo(strip_trailing_zeros<Is...>{}))
 { };

int main()
 {
   A<1, 0>{}; // print "1" (inherit from B<1>)
   A<1, 1>{}; // print "1, 1" (inherit from B<1, 1>)
   A<1, 2, 3, 0, 0, 0>{}; // print "1, 2, 3" (inherit from B<1, 2, 3>)
   A<1, 2, 3>{}; // // print "1, 2, 3" (inherit from B<1, 2, 3>)
 }

No, after-pack-deduction pattern matching does not work in C++.

Nothing is ever successfully matched after a pack is deduced. Ever.

A pattern-match can occur after a pack is expanded, but the pack cannot deduce types and then go on to match anything.

A seperate list is the best you can do.

Related