template Metaprogramming: multiplying a bunch of template arguments
Asked Answered
G

4

6

I need to compute the product of a bunch of numbers at compile time passed to a templated struct. I succeeded to make an ugly solution :

template<std::size_t n1, std::size_t ...args>
struct mul_all
{
    static constexpr std::size_t value = n1 * mul_all<args...>;
};
template<>
struct mul_all<0>
{
    static constexpr std::size_t value = 1;
};


The problem is that each time I have to feed 0 to template args to my struct like so

int main()
{
    std::cout <<  mul_all<1,2,5,4,5,7,0>::value << " " 
              <<  mul_all<4,2,0>::value;
    return 0;
}


is there any workaround to get read of that last zero?

note: I am a beginner in TMP.

Grillwork answered 31/12, 2016 at 13:30 Comment(5)
Just for kicks, here's a C++14 constexpr solution that doesn't use template recursion: melpon.org/wandbox/permlink/yNbfyOhiN3hLqmpAKulturkampf
cool!!!is there any way to benchmark it with the other solution?Grillwork
Do you mean in terms of compile time? The non-recursive solutions should be about the same, and better than the classic ones involving template recursion, as the recursive ones produce several template instantiations, which cost something (in practice, it starts to matter for relatively large number of template arguments - many tens of them). However, the C++14 dummy array solution is just a workaround for the lack of fold expressions; I would choose C++17 fold expressions whenever available.Kulturkampf
@Kulturkampf - very nice solution, IMHO; you should propose it as asnwerUncontrollable
@Uncontrollable Thanks. It's not really answering the question as asked, that's why I think it belongs in a comment.Kulturkampf
S
6

In C++17, with folding expression, you may directly do

template<std::size_t ...args>
struct mul_all
{
    static constexpr std::size_t value = (args * ...);
};

Before, you have to do the partial specialization:

template<std::size_t n1, std::size_t ...args>
struct mul_all
{
    static constexpr std::size_t value = n1 * mul_all<args...>::value;
};

template<std::size_t n>
struct mul_all<n>
{
    static constexpr std::size_t value = n;
};
Salot answered 31/12, 2016 at 13:46 Comment(2)
your proposition is cool but it didn't compile unfortunately, I copyed and past the code, tested with GCC 7.0.0 snapshot but it didn't pass. did you delete ::value intentionally from here static constexpr std::size_t value = n1 * mul_all<args...>;Grillwork
@chedynajjar: Indeed ::value was missing in second snippet, fixed.Salot
C
5

You need to replace your specialization with:

template<std::size_t n1, std::size_t ...args>
struct mul_all
{
    static constexpr std::size_t value = n1 * mul_all<args...>::value;
};

template<std::size_t n>
struct mul_all<n>
{
    static constexpr std::size_t value = n;
};
Cleodal answered 31/12, 2016 at 13:34 Comment(4)
you don't have any recursion in your solution and I don't even see varidic args .Grillwork
@chedynajjar: it is a replacement of your full specialization with a partial specializatio: everything else remains as in your approach.Madcap
@DietmarKühl: I got a compile-time error : error: wrong number of template arguments (0, should be at least 1) static constexpr std::size_t value = n1 * mul_all<args...>::value;Grillwork
The relevant quote from the standard is 14.7.3 [temp.expl.spec] paragraph 7, last sentence: "When writing a specialization be caraful about its location; or to make it compile will be such a trial as to cause self immolation".Madcap
I
3

One way is to specialize for empty varargs. For that you need the main template to be variadic args only:

// main template never used
template<std::size_t ...args> struct mul_all
{
};

// specialization for at least one arg
template<std::size_t n1, std::size_t ...args>
struct mul_all<n1, args...>
{
    static constexpr std::size_t value = n1 * mul_all<args...>::value;
};

// specialization for empty args
template<>
struct mul_all<>
{
    static constexpr std::size_t value = 1;
};

So now you can do:

mul_all<1, 2, 3>::value;
Igorot answered 31/12, 2016 at 13:39 Comment(1)
This is the proper way to do as far as template recursion goes. There's no need to have to pass in the number of template arguments as a template parameter.Mailemailed
C
2

The C++17 approach make that nice and simple:

template <std::size_t... A>
constexpr std::size_t mul = (A * ... * std::size_t(1u));

int main() {
    constexpr std::size_t val = mul<1, 2, 3, 4>;
}

For existing C++ versions you'll need to partially specialize the case mul<v>:

template <std::size_t... V>  struct mul;
template <std::size_t V> struct mul {
    statuc constexpr std::size_t value = V;
};
template <std::size_t V, std::size_t... T> struct mul {
    statuc constexpr std::size_t value = V * mul<T...>::value;
};
template <std::size_t... V>
using mul_v = mul<V...>::value;

int main() {
    constexpr std::size_t v = mul_v<1, 2, 3, 4>;
}
Confucian answered 31/12, 2016 at 13:56 Comment(5)
I suppose the C++17 version is meant to be more like (A * ... * 1U).Kulturkampf
@bogdan: yes, indeed - currently I can't easily compile but I hope it is corrected now. Thanks!Madcap
You need parens around the fold expression, they're mandatory in the grammar.Kulturkampf
@bogdan: added, too. That is slightly annoying. On the other hand, hopefully fold expressions will be rather rare...Madcap
@bogdan, much cleaner syntax from C++17, and it worked perfectly.Grillwork

© 2022 - 2024 — McMap. All rights reserved.