From several integral constants, it is possible to define a trait Sum
that returns the sum of all constants. For instance, we can define the following:
#include <type_traits>
template<typename...ARGS>
struct Sum : std::integral_constant<int,0> {};
template<typename T, typename...ARGS>
struct Sum<T,ARGS...> : std::integral_constant<int,T::value + Sum<ARGS...>::value> {};
struct One : std::integral_constant<int,1> {};
struct Two : std::integral_constant<int,2> {};
struct Three : std::integral_constant<int,3> {};
static_assert (Sum<One,Two,Three>::value == 6);
Q1: does the standard library hold such a trait ? (I mean, I don’t want to reinvent the wheel)
Q2: would it be possible to define this trait without using template specialization ?
4
In Boost.Mp11 this is a one-liner (as always):
struct One : std::integral_constant<int,1> {};
struct Two : std::integral_constant<int,2> {};
struct Three : std::integral_constant<int,3> {};
static_assert(mp_plus<One,Two,Three>::value == 6);
Which, yes, you can implement without specialization by using a fold-expression
// useful thing to have around regardless
template <auto V>
using Constant = std::integral_constant<decltype(V), V>;
template <typename... Ts>
using Sum = Constant<(Ts::value + ... + 0)>;
The + 0
is a trick to ensure that Sum<>
gives you Constant<0>
which is integral_constant<int, 0>
. Otherwise, this preserves the actual types of the input properly without you having to type them yourself.
2
Does the standard library hold such a trait?
No, the C++ standard library does not provide a direct equivalent to the Sum
trait you’ve defined.
Would it be possible to define this trait without using template specialization?
There are many ways. One way with minimal change to your original code, would be:
template<typename... Args>
struct Sum : std::integral_constant<int, (0 + ... + Args::value)> {};
struct One : std::integral_constant<int, 1> {};
struct Two : std::integral_constant<int, 2> {};
struct Three : std::integral_constant<int, 3> {};
static_assert(Sum<One, Two, Three>::value == 6);
static_assert(Sum<>::value == 0); // Empty sum is zero
Or with variable template and template lambda (requires compiler supports c++20 or later)
template<int N> using Int = std::integral_constant<int, N>;
constexpr auto Sum = []<typename... Args>() { return (0 + ... + Args::value); };
static_assert(Sum.template operator()<Int<1>, Int<2>, Int<3>> () == 6);
static_assert(Sum.template operator()<>() == 0); // Empty sum is zero
See live demo
Or just use a simple template function for calculation (Requires compiler support c++17 or higher, because of fold expressions usage).
template<int N> using Int = std::integral_constant<int, N>;
template<typename... Args> constexpr auto Sum() { return (0 + ... + Args::value); }
static_assert(Sum<Int<1>, Int<2>, Int<3>>() == 6);
static_assert(Sum<>() == 0); // Empty sum is zero
Since you have C++20, you also have consteval
to turn a std::integral_constant
in just a compile time int
, which in turns allows you to use ordinary <algorithms>
like std::accumulate
to preferom the summation:
#include <array>
#include <numeric>
#include <type_traits>
template<int ...I>
consteval auto sum(std::integral_constant<int, I> const& ...) {
constexpr auto a = std::array{I...};
return std::accumulate(a.begin(), a.end(), 0);
}
struct One : std::integral_constant<int,1> {};
struct Two : std::integral_constant<int,2> {};
struct Three : std::integral_constant<int,3> {};
static_assert (sum(One{}, Two{}, Three{}) == 6);
Sad we don’t have ranges algos, otherwise sum
body would be a oneliner:
return std::ranges::accumulate(std::array{I...}, 0);
Very interesting talk related to this: CppCon 2019: David Stone – Removing Metaprogramming From C++, Part 1 of N: constexpr Function Params.