初始化带有静态存储时间的std::数组,带有参数Pack扩展和附加值
Initializing std::array with Static Storage Duration with a Parameter Pack Expansion and an Additional Value
最近在问另一个问题时,我偶然发现GCC在初始化std::array
时使用参数包扩展后跟另一个元素时的一些奇怪行为。我已经在评论中与Jarod42简要地讨论过这个问题,但我认为最好把它作为一个新问题来问。
make_array
函数,该函数接受任意数量的参数,std::forward
将它们传递给std::array
初始化。前导标记参数选择数组是否应该以默认构造的T
(通过std::true_type
选择)结束(通过std::false_type
选择)。然后,我创建任何一种类型的整数数组一次与静态和一次与自动存储持续时间。数组元素最终被打印出来。
#include <array> // std::array
#include <cstddef> // std::size_t
#include <iomanip> // std::setw
#include <ios> // std::left, std::right
#include <iostream> // std::cout
#include <string> // std::string
#include <type_traits> // std::false_type, std::true_type
// This is only used for visualization.
template <typename T, std::size_t N>
void
print_array(const std::string& name, const std::array<T, N>& arr)
{
std::cout << std::setw(20) << std::left << (name + ":") << std::right << "{";
for (auto iter = arr.cbegin(); iter != arr.cend(); ++iter)
std::cout << (iter != arr.cbegin() ? ", " : "") << std::setw(2) << *iter;
std::cout << "}n";
}
// Create a `std::array<T>` with elements constructed from the provided
// arguments.
template <typename T, typename... ArgTs>
static constexpr auto
make_array(std::false_type, ArgTs&&... args) noexcept
{
std::array<T, sizeof...(args)> values = { { std::forward<ArgTs>(args)... } };
return values;
}
// Create a `std::array<T>` with elements constructed from the provided
// arguments followed by a default-constructed `T`.
template <typename T, typename... ArgTs>
static constexpr auto
make_array(std::true_type, ArgTs&&... args) noexcept
{
std::array<T, sizeof...(args) + 1> values = {
{ std::forward<ArgTs>(args)..., T {} }
};
return values;
}
namespace /* anonymous */
{
const auto values_no_static = make_array<int>(std::false_type(), 1, 2, 3, 4);
const auto values_yes_static = make_array<int>(std::true_type(), 1, 2, 3, 4);
}
int
main()
{
const auto values_no_automatic = make_array<int>(std::false_type(), 1, 2, 3, 4);
const auto values_yes_automatic = make_array<int>(std::true_type(), 1, 2, 3, 4);
print_array("static yes", values_yes_static);
print_array("static no", values_no_static);
print_array("automatic yes", values_yes_automatic);
print_array("automatic no", values_no_automatic);
}
我希望看到数组{1, 2, 3, 4}
和{1, 2, 3, 4, 0}
打印两次。Clang就是这么做的。但是,GCC会为终止的具有静态存储持续时间的数组打印{0, 0, 0, 0, 0}
。如果我将初始化列表中末尾的T {}
替换为-1
,我将得到{0, 0, 0, 0, -1}
。因此,添加尾随元素似乎会导致参数包扩展为全零。但前提是生成的std::array
具有静态存储持续时间。
我调用了未定义的行为还是这是GCC中的一个bug ?如果这是未定义的行为,我将感谢官方对标准的参考。我已经知道一种简单的方法来解决这个问题(参见我对上面提到的问题的回答),我对避免这个问题不感兴趣。相反,我想知道代码是否格式良好,如果是,哪个编译器是正确的。
使用GCC完成输出:
$ g++ --version
g++ (GCC) 5.1.0
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$
$ g++ -std=c++14 -o main_gcc -Wall -Wextra -Werror -pedantic main.cxx
$
$ ./main_gcc
static yes: { 0, 0, 0, 0, 0}
static no: { 1, 2, 3, 4}
automatic yes: { 1, 2, 3, 4, 0}
automatic no: { 1, 2, 3, 4}
使用铿锵声:$ clang --version
clang version 3.6.2 (tags/RELEASE_362/final)
Target: x86_64-unknown-linux-gnu
Thread model: posix
$
$ clang -std=c++14 -o main_clang -Wall -Wextra -Werror -pedantic main.cxx -lstdc++
$
$ ./main_clang
static yes: { 1, 2, 3, 4, 0}
static no: { 1, 2, 3, 4}
automatic yes: { 1, 2, 3, 4, 0}
automatic no: { 1, 2, 3, 4}
最小复制样本:
#include <array>
constexpr auto make_array(int i)
{
std::array<int, 2> values = { i, 0 };
return values;
}
constexpr auto arr = make_array(1);
static_assert(arr[0] == 1, "");
Demo with HEAD。到目前为止,我们可以有把握地说,没有引起UB。
注意我们是如何解决这个bug的:
将
0
更改为1
修改
return
语句返回一个相应构造的临时代替
i
,使用整数字面值作为第一个初始化子句
我已经向GCC开发人员报告了这一点。它被确认为错误67104,并于2015年8月修复了GCC 6,并向后移植到GCC 5.3。您可以使用Columbo回答中提供的在线编译器链接来检查。
相关文章:
- 如何反转整数参数包
- 使用C++库在Android项目中修改gradle中的cmake参数,用于插入指令的测试
- 如何使用默认参数等选择模板专业化
- 模板参数替换失败,并且未完成隐式转换
- 具有默认模板参数的多态类的模板推导失败
- lambda参数转换为constexpr技巧,然后获取带链接的数组
- 将数组作为参数传递给函数安全吗?作为第三方职能部门,可以探索他们想要的之外的其他元素
- 函数调用中参数的顺序重要吗
- 部分定义/别名模板模板参数
- 模板-模板参数推导:三个不同的编译器三种不同的行为
- 使用不带参数的函数访问结构元素
- 基于另一个成员参数将函数调用从类传递给它的一个成员
- 如何在OMNET++中指定与命令行参数组合的输出文件名
- 如何使用Luacneneneba API正确读取字符串和表参数
- 在派生函数中指定void*参数
- 视图中的参数推导失败:take_while
- static_assert在宏中,但也可以扩展到可以用作函数参数的东西
- 使用指向成员的指针将成员函数作为参数传递
- 传递参数pack来替换stl函数会导致编译错误
- 初始化带有静态存储时间的std::数组,带有参数Pack扩展和附加值