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

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

在我的 class(这是一个可变 class 模板)中,我需要一个 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 年,我遇到了编译错误 "expression did not evaluate to a constant."

我不确定是什么不允许表达式保持不变。我尝试编译不同的东西以确保它们可以保持不变。我已经尝试在 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?

"expression did not evaluate to a constant." 似乎不是根本原因。您的 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

你的代码的问题是,当你用一个 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 开始使用。

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

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

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

另一个建议:sizeof() 是一个 std::size_t 值,所以如果 maxSizeOf() return 一个 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()的ground case(零型版本)已经不用了,可以删掉

无论如何,正如 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;
 }