为什么变参函数模板中的constexpr不恒定?

时间:2019-01-22 04:03:02

标签: c++ c++11 variadic-templates template-meta-programming constexpr

在我的班级(这是一个可变参数类模板)中,对于可变参数模板中传递的最大类型的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中,我收到一个编译错误“表达式未求值为常数”。

我不确定是什么不允许表达式保持不变。我尝试编译不同的东西以确保它们可以保持不变。我尝试在sizeof()函数中使用带有模板参数的constexpr,该函数可以正常工作,因为在编译时总是知道类型的大小,所以我希望这样做。整数算术和比较似乎在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

2 个答案:

答案 0 :(得分:2)

代码的问题是,当您使用单个max_sizeof<T>()类型调用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...>());
};

个匹配项。因此,编译器无法选择正确的版本。

您可以按照{tnpanic的建议,使用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...列表为空)时,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;
 }

答案 1 :(得分:1)

“表达式的计算结果不为常数。”似乎不是根本原因。您的static_maxmaxSizeOf需要进行修改以使编译器满意。您可以参考this post来了解如何在不同的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 ...就像内森在评论中提到的那样,std::max也可以处理initializer_list