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>
The template parameter list and the template argument list of a member of a partial specialization must match the parameter list and the argument list of the partial specialization. Just like with members of primary templates, they only need to be defined if used in the program.
Explicit (full) specialization of a member of a partial specialization is declared the same way as an explicit specialization of the primary template. If a primary template is a member of another class template, its partial specializations are members of the enclosing class template.
the first function template has the same template parameters as the first partial specialization and has just one function parameter, whose type is a class template specialization with all the template arguments from the first partial specialization
1) If only one specialization matches the template arguments, that specialization is used 2) If more than one specialization matches, partial order rules are used to determine which specialization is more specialized. The most specialized specialization is used, if it is unique (if it is not unique, the program cannot be compiled)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With