Reputation: 138
In my class (which is a variadic class template), I need a constexpr
for the sizeof()
of the largest type passed in a variadic template. Like this:
template<class... Types>
class DiscriminatedUnion
{
.
.
.
static constexpr auto value = maxSizeOf<Types...>();
The code I have come up with for maxSizeOf()
is the following:
template <class T>
static constexpr T static_max(T a, T b) {
return a < b ? b : a;
}
template <class T, class... Ts>
static constexpr T static_max(T a, Ts... bs) {
return static_max(a, static_max(bs...));
}
template <class T>
static constexpr int maxSizeOf() {
return sizeof(T);
};
template <class T, class... Ts>
static constexpr int maxSizeOf() {
return static_max(sizeof(T), maxSizeOf<Ts...>());
};
But in Visual Studio 2017, I am getting a compile error "expression did not evaluate to a constant."
I am not sure what it is that is not allowing the expression to be constant. I've tried compiling different things to ensure that they can be constant. I've tried using a sizeof()
with a template parameter in a constexpr
function, which works, which I expect since the size of types is always known at compile time. Integer arithmetic and comparison seems to be valid in a constexpr
function, but I tried some again for verification. Then I've tried using integer arithmetic in the variadic template method, without sizeof()
, with the following:
template <class T>
static constexpr int maxSizeOf(int n) {
return n;
};
template <class T, class... Ts>
static constexpr int maxSizeOf(int n) {
return static_max(n, maxSizeOf<Ts...>(n + 1));
};
static constexpr int numBytes = maxSizeOf<Types...>(1);
And this does not work. So I'm thinking it must be something to do with the variadic method template expansion. But this should be able to be made a compile-time constant, because variadic template packs are always expanded at compile time. Does anyone know why these can't be constexpr
?
Upvotes: 1
Views: 1084
Reputation: 66200
The problem of your code is that when you call max_sizeof<T>()
with a single T
types, both
template <class T>
static constexpr int maxSizeOf() {
return sizeof(T);
};
and
template <class T, class... Ts>
static constexpr int maxSizeOf() {
return static_max(sizeof(T), maxSizeOf<Ts...>());
};
matches. So the compiler can't choose the correct one.
You can solve with if constexpr ( sizeof...(Ts) )
, as suggested by dontpanic, but if constexpr
is available only starting from C++17.
A possible (and elegant, IMHO) solution, working also in C++11 and C++14, is delete the only-one-type function and add the following zero-type function
template <int = 0>
static constexpr std::size_t maxSizeOf()
{ return 0u; };
This way when you call maxSizeOf<Ts...>()
, when sizeof...(Ts) > 0u
, the one-or-more-type version is called; when sizeof...(Ts) == 0u
(that is: when the Ts...
list is empy), the int = 0
(no types) matches.
Onother suggestion: sizeof()
is a std::size_t
value so is better if maxSizeOf()
return a std::size_t
The following is a full working (also C++11) solution
#include <iostream>
template <typename T>
static constexpr T static_max (T a, T b)
{ return a < b ? b : a; }
template <typename T, typename ... Ts>
static constexpr T static_max (T a, Ts ... bs)
{ return static_max(a, static_max(bs...)); }
template <int = 0>
static constexpr std::size_t maxSizeOf()
{ return 0u; };
template <typename T, typename ... Ts>
static constexpr std::size_t maxSizeOf()
{ return static_max(sizeof(T), maxSizeOf<Ts...>()); };
template <typename ... Ts>
struct foo
{ static constexpr auto value = maxSizeOf<Ts...>(); };
int main ()
{
std::cout << foo<int, long, long long>::value << std::endl;
}
But, as observed by aschepler (thanks!), this solution works but doesn't use at all the variadic version of static_max()
.
An alternative, that uses the variadic version of static_max()
, is rewrite the variadic version of maxSizeOf()
not in a recursive way but simply unpacking the variadic list as follows
template <typename ... Ts>
static constexpr std::size_t maxSizeOf()
{ return static_max(sizeof(Ts)...); }
Now is the ground case (the zero-type version) of maxSizeOf()
that isn't used anymore and can be deleted.
Anyway, as suggested by NathanOliver, you can use std::max()
(the version receiving an initializer list) that, starting from C++14, is constexpr
.
So, starting from C++14, you can simply write
#include <algorithm>
#include <iostream>
template <typename ... Ts>
struct foo
{ static constexpr auto value = std::max({sizeof(Ts)...}); };
int main ()
{
std::cout << foo<int, long, long long>::value << std::endl;
}
Upvotes: 2
Reputation: 351
"expression did not evaluate to a constant." seems not the root cause. Your static_max
and maxSizeOf
need modifications to make the compiler happy. You can refer to this post to see how to do that under different C++ standards.
For example :
template <class T, class... Ts>
static constexpr T static_max(T a, Ts... bs) {
if constexpr (sizeof...(Ts) == 0)
return a;
else
return std::max(a, static_max(bs...));
}
template <class T, class... Ts>
static constexpr int maxSizeOf(int n) {
if constexpr (sizeof...(Ts) == 0)
return n;
else
return static_max(n, maxSizeOf<Ts...>(n + 1));
};
Actually, we don't need static_max
at all. All we need here is to find the max within just 2 values, and std::max
is already there.
EDIT: it seems that we don't need maxSizeOf
either... as Nathan mentioned in the comments, std::max
can deal with initializer_list
as well.
Upvotes: 1