静态断言 std::array 的大小,其类型是使用 decltype 从成员函数的返回值中获取的

Statically asserting the size of a std::array whose type is obtained using decltype from the return value of a member function

本文关键字:decltype 成员 返回值 获取 函数 类型 array std 断言 静态      更新时间:2023-10-16

我想编写一个自由函数,该函数可以根据类的成员函数的返回值自动确定其参数的类型。 使用decltype,这部分很容易。

我还希望有一个编译时断言来验证对该参数类型的假设,这就是我提出的解决方案失败的地方。

请考虑以下 MCVE:

#include <type_traits>
#include <array>
#include <iostream>
class Foo
{
public:
std::array<int, 10> Get();
};
void PrintFoos(const decltype(Foo().Get())& param)
{
static_assert(param.size() == 10, "wrong size");
for (const auto& i : param)
{
std::cout << i << "n";
}
}

GCC 编译上面的代码很好,没有警告。

另一方面,叮当抱怨道:

error: static_assert expression is not an integral constant expression
static_assert(param.size() == 10, "wrong size");
^~~~~~~~~~~~~~~~~~

MSVC也是如此:

(13): error C2131: expression did not evaluate to a constant
(13): note: failure was caused by a read of a variable outside its lifetime
(13): note: see usage of 'param'

为什么GCC编译这个很好,而其他编译器拒绝它?是否有我受益于的 GCC 扩展来支持这一点?

语言标准对此有何规定?我的目标是C++17,但也有兴趣知道C++14是否有任何变化。

奖励问题:有没有办法修改此代码以使其工作? 显然,如果decltype表达式的计算结果不是std::array类型,则static_assert应该失败,因为size()成员函数不会constexpr。我想有一个解决方案涉及添加模板帮助程序函数,但除非绝对必要,否则我宁愿不添加另一个函数定义。

我相信clang和其他人(icc和msvc)在技术上是正确的,而GCC是错误的。static_assert声明采用常量表达式[expr.const]/2。我认为手头案件的相关C++17措辞应该是[expr.const]/2.11:

表达式e核心常量表达式,除非按照抽象机器的规则计算e将计算以下表达式之一:

  • [...]
  • 引用
  • 引用类型的变量或数据成员的ID 表达式,除非引用具有前面的初始化,并且
    • 它使用常量表达式或
    • 它的生命周期始于对e的评估;
  • [...]

但是,上面static_assert中的表达式显然正是这样做的(param是一个引用引用类型变量的id 表达式,因此没有任何例外适用)。因此,它不是一个常量表达式,并且程序格式不正确 [dcl.dcl]/6。C++14标准中的相关措辞似乎是相同的。我认为这是 GCC 中的一个错误。

如果可以将函数更改为模板,则可以简单地推断大小:

template <int N>
void PrintFoos(const std::array<int, N>& param)
{
…
}

或者,如果你想让一切都依赖于Foo,你也可以定义一个公共常量并从中派生数组类型等:

class Foo
{
public:
static constexpr auto size = 10;
std::array<int, size> Get();
};
void PrintFoos(const decltype(Foo().Get())& param)
{
static_assert(Foo::size == 10, "wrong size");
}

当然,您可以使用帮助程序模板:

template <typename T>
constexpr std::size_t deduce_array_size = 0U;
template <typename T, std::size_t N>
constexpr std::size_t deduce_array_size<std::array<T, N>> = N;
template <typename T>
constexpr std::size_t deduce_array_size<T&> = deduce_array_size<T>;
template <typename T>
constexpr std::size_t deduce_array_size<T&&> = deduce_array_size<T>;

然后

void PrintFoos(const decltype(Foo().Get())& param)
{
static_assert(deduce_array_size<decltype(param)> == 10, "wrong size");
}

最后,另一种选择(灵感来自下面的 Yakk - Adam Nevraumont 的评论)是简单地在您的常量表达式中创建数组类型的 prvalue,并询问其大小:

static_assert(std::decay_t<decltype(param)>{}.size() == 10, "wrong size");