为什么可变参数函数模板中的这个 constexpr 不是常数?

Why isn't this constexpr in a variadic function template constant?

本文关键字:constexpr 常数 变参 参数 函数模板 为什么      更新时间:2023-10-16

在我的类(这是一个可变类模板)中,我需要一个constexpr作为可变模板中传递的最大类型的sizeof()。像这样:

template<class... Types>
class DiscriminatedUnion
{
.
.
.
static constexpr auto value = maxSizeOf<Types...>();

我为maxSizeOf()想出的代码如下:

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...>());
};

但在Visual Studio 2017中,我收到了一个编译错误"表达式未计算为常量"。

我不确定是什么不允许表达式保持不变。我试着编译不同的东西,以确保它们可以是恒定的。我曾尝试在constexpr函数中使用带有模板参数的sizeof(),这是有效的,因为在编译时类型的大小总是已知的,所以我希望这一点。整数运算和比较在constexpr函数中似乎是有效的,但我再次尝试了一些以进行验证。然后,我尝试在变量模板方法中使用整数算术,不使用sizeof(),并使用以下内容:

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);

这是行不通的。所以我认为这一定是与可变方法模板扩展有关。但这应该能够成为编译时常数,因为可变模板包总是在编译时扩展的。有人知道为什么这些不能是constexpr吗?

代码的问题是,当您使用单个T类型调用max_sizeof<T>()时,两个

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...>());
};

匹配。因此编译器无法选择正确的。

正如dontpanic所建议的那样,您可以使用if constexpr ( sizeof...(Ts) )进行求解,但if constexpr只能从C++17开始使用。

一个可能的(也是优雅的,IMHO)解决方案,也适用于C++11和C++14,是删除唯一的一个类型函数,并添加以下零类型函数

template <int = 0>
static constexpr std::size_t maxSizeOf()
{ return 0u; };

这样,当您调用maxSizeOf<Ts...>()时,当sizeof...(Ts) > 0u时,会调用一个或多个类型版本;当sizeof...(Ts) == 0u时(即:当Ts...列表为empy时),int = 0(无类型)匹配。

另一个建议:sizeof()std::size_t值,因此如果maxSizeOf()返回std::size_t更好

以下是一个完整的工作(也是C++11)解决方案

#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;
}

但是,正如aschepler所观察到的(谢谢!),这个解决方案有效,但根本不使用static_max()的可变版本。

另一种使用static_max()的可变版本的方法是重写maxSizeOf()的可变版本,不是以递归的方式,而是简单地拆包可变列表,如下所示

template <typename ... Ts>
static constexpr std::size_t maxSizeOf()
{ return static_max(sizeof(Ts)...); } 

现在是maxSizeOf()的基本情况(零型版本),它不再使用,可以删除。

无论如何,正如NathanOliver所建议的,您可以使用std::max()(接收初始值设定项列表的版本),从C++14开始,它就是constexpr

因此,从C++14开始,您可以简单地编写

#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;
}

"表达式未计算为常量。"似乎不是根本原因。您的static_maxmaxSizeOf需要进行修改以使编译器满意。你可以参考这篇文章,看看如何在不同的C++标准下做到这一点。

例如:

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));
};

实际上,我们根本不需要static_max。这里我们只需要在2个值内找到最大值,并且std::max已经存在。

编辑:看来我们也不需要maxSizeOf。。。正如Nathan在评论中提到的,std::max也可以处理initializer_list