I have a variadic template class SomeClass
that looks like this (basically):
template<std::size_t SIZE_>
class SomeClass {
public:
static constexpr std::size_t SIZE = SIZE_;
};
It has a constexpr member that simply contains the std::size_t
template parameter used to instantiate it. I want a constexpr
function that can sum all the sizes of SomeClass<SIZE_>
specializatins. My first idea was to make a simple variadic template function that adds all the SIZE
s like that:
template<typename T>
constexpr std::size_t totalSize() {
return T::SIZE;
}
template <typename T, typename... Ts>
constexpr std::size_t totalSize() {
return totalSize<T>() totalSize<Ts...>();
}
Now I try to call it:
constexpr std::size_t size = totalSize<SomeClass<1>, SomeClass<2>, SomeClass<3>>() // should return 6
It turns at that the last parameter unpacking makes call to totalSize<T>
function ambiguous because both template totalSize
functions match it. Fine, I modified my code to have two distinct functions and came up with this:
template<typename T>
constexpr std::size_t totalSizeSingle() {
return T::SIZE;
}
template <typename T, typename... Ts>
constexpr std::size_t totalSize() {
std::size_t result = totalSizeSingle<T>();
if (sizeof...(Ts) > 0) result = totalSize<Ts...>();
return result;
}
Again, the last unpacking seem to be problematic. Apparently, T
can't be deduced even though it is known at compile time. I get the following error (compiling with GCC):
In instantiation of 'constexpr std::size_t totalSize() [with T = SomeClass<3>; Ts = {}; std::size_t = long long unsigned int]':
main.cpp:129:51: required from here
main.cpp:134:82: in 'constexpr' expansion of 'totalSize<SomeClass<1>, SomeClass<2>, SomeClass<3> >()'
main.cpp:129:51: in 'constexpr' expansion of 'totalSize<SomeClass<2>, SomeClass<3> >()'
main.cpp:129:58: error: no matching function for call to 'totalSize<>()'
129 | if (sizeof...(Ts) > 0) result = totalSize<Ts...>();
| ~~~~~~~~~~~~~~~~^~
main.cpp:127:23: note: candidate: 'template<class T, class ... Ts> constexpr std::size_t totalSize()'
127 | constexpr std::size_t totalSize() {
| ^~~~~~~~~
main.cpp:127:23: note: template argument deduction/substitution failed:
main.cpp:129:58: note: couldn't deduce template parameter 'T'
129 | if (sizeof...(Ts) > 0) result = totalSize<Ts...>();
| ~~~~~~~~~~~~~~~~^~
I don't really understand why it is not working. Every type is know at compile time. Why is this happening and how can I get it to work? I'm using C 11.
CodePudding user response:
There's a reason people call struct
s metafunctions in c template metaprogramming.
In our case the major advantage of doing metaprogramming in a struct is that resolving function overloading byy template arguments is different and more limited than that of class specialisation.
So I suggest:
template <class ...> // only called if there's no arguments
struct add_sizes {
static constexpr auto value = 0;
};
template <class T, class ... Ts>
struct add_sizes <T, Ts...> {
static constexpr auto value = T::SIZE add_sizes<Ts...>::value;
};
// then wrap it into your function:
template <class ... Ts>
constexpr std::size_t totalSize () {
return add_sizes<Ts...>::value;
}
Another advantage of using structs is for easy "return" of multiple values or types, which for functions gets complicated. Sometimes by calculating A you've already calculated B, so it can make sense to store both.
In general I'd say that if you're ever struggling solving a metaprogramming problem with functions, switch to structs.
CodePudding user response:
The problem is when the template argument list is of size 1 totalSize<Ts...>();
would call totalSize<>()
but totalSize<>();
is not defined.
You could make your first version work by making it take 2 or make template arguments.
template<typename T>
constexpr std::size_t totalSize() {
return T::SIZE;
}
template <typename T, typename U, typename... Ts>
constexpr std::size_t totalSize() {
return totalSize<T>() totalSize<U, Ts...>();
}