std::数组指针在constexpr上下文中的Size()

size() of std::array pointer in constexpr context

本文关键字:Size constexpr 数组 指针 std 上下文      更新时间:2023-10-16

假设我有一个函数:

int test(std::array<char, 8>* data) {
  char buffer[data->size() * 2];
  [... some code ...]
}

显然缓冲区的大小可以在编译时计算:数据的constexpr大小为8个元素,8 * 2 = 16字节。

然而,当用-Wall, -pedantic-std=c++11编译时,我得到了臭名昭著的错误:

警告:可变长度数组是C99特性[-Wvla-extension]

我认为这是有意义的:array::size()constexpr,但它仍然是一个方法,在上面的函数中,我们仍然需要解引用一个指针,这不是constexpr

如果我尝试这样做:

int test(std::array<char, 8>& data) {
  char buffer[data.size() * 2];
  [...]
}

gcc(试用版本5.2.0)似乎很高兴:没有警告。

但是与clang++(3.5.1)我仍然得到一个警告抱怨可变长度数组。

在我的情况下,我不能轻易地改变test的签名,它必须接受一个指针。所以…几个问题:

  1. 在constexpr上下文中获得std::array 指针的大小的最佳/最标准方法是什么?

  2. 指针与引用的行为是否有差异?哪个编译器对gccclang的警告是正确的?

我不知道2.

但是对于1,我们可以这样做:

template<class T, size_t N>
constexpr std::integral_constant<size_t, N> array_size( std::array<T, N> const& ) {
  return {};
}

:

void test(std::array<char, 8>* data) {
  using size=decltype(array_size(*data));
  char buffer[size{}];
  (void)buffer;
  // [... some code ...]
}

另外:

template<class T, class U, size_t N>
std::array<T,N> same_sized_array( std::array< U, N > const& ) {
  return {};
}
void test(std::array<char, 8>* data) {
  auto buffer = same_sized_array<char>(*data);
  (void)buffer;
  // [... some code ...]
}

最后,c++ 14的清理:

template<class A>
constexpr const decltype(array_size( std::declval<A>() )) array_size_v = {};
void test3(std::array<char, 8>* data) {
  char buffer[array_size_v<decltype(*data)>];
  (void)buffer;
  // [... some code ...]
}

生活例子。

好的老C方法是定义,但c++有const int或c++ 11的constexpr。所以,如果你想让编译器知道数组的大小是一个编译时常数,最可移植的(*)方法是将其设置为constconstexpr:

#include <iostream>
#include <array>
const size_t sz = 8;  // constexpr size_t sz for c++11
int test(std::array<char, sz>* data) {
  char buffer[sz * 2];
  buffer[0] = 0;
  return 0;
}
int main()
{
    std::array<char, sz> arr = { { 48,49,50,51,52,53,54,55 } };
    int cr = test(&arr);
    std::cout << cr << std::endl;
    return 0;
}

即使在Clang 3.4.1下使用-Wall -pedantic,编译时也没有警告。

对于第二个问题,我无法想象为什么gcc在这里区分指针和参考。它可以确定大小为常量的std::array上的size()方法是常量表达式——它应该允许两者——或者不允许——它应该对两者发出相同的警告。但它不仅涉及编译器,还涉及标准库的实现。

真正的问题是c++ 11之前的std::array不是标准库的一部分,而且constexpr也是从c++ 11开始才定义的。因此,在c++ 11之前的模式中,两个编译器都将std::array作为扩展处理,但是size方法无法将其返回值声明为常量expr。这就解释了为什么Clang(和gcc面对一个指针)会发出警告。

但是如果你在c++11模式下编译原始代码(-std=c++11),你应该没有警告,因为标准要求std::array上的size()方法必须是constexpr

(*)问题是关于最好/最标准的;我不能说什么是最好的方式,我也不能定义最标准的,所以我坚持如果我想避免在非c++ 11编译器上的可移植性问题,我将使用什么。

在参数的decltype上使用std::tuple_size怎么样?

void test(std::array<char, 8>* data) {
    using data_type = std::remove_pointer<decltype(data)>::type;
    char buffer[std::tuple_size<data_type>::value * 2];
    static_assert(sizeof buffer == 16, "Ouch");
    // [... some code ...]
}