问题描述
当我尝试使用特定的编译器版本进行编译时出现编译器错误。
IE。带有 -std=c++17 -O3
编译器错误:
source>(19): error: no suitable user-defined conversion from "Data" to "std::__cxx11::string" exists
Data temp{std::forward<Data>(d)};
^
compilation aborted for <source> (code 2)
ASM generation compiler returned: 2
<source>(19): error: no suitable user-defined conversion from "Data" to "std::__cxx11::string" exists
Data temp{std::forward<Data>(d)};
^
compilation aborted for <source> (code 2)
Execution build compiler returned: 2
代码:
#include <string>
#include <vector>
#include <iostream>
struct Data {
std::string id{};
std::string rowData{};
int totalRawDataLength{};
std::vector<int> rawDataOffset{};
std::vector<int> rawDataLength{};
Data() = default;
Data(const Data&d) =default;
Data(Data &&d) =default;
};
Data ProcessData(Data &&d) {
Data temp{std::forward<Data>(d)};
// some code
return temp;
}
int main() {
Data d{};
d.id = "id_001";
d.rowData = "some data";
d.rawDataOffset.emplace_back(4);
d.rawDataLength.emplace_back(4);
auto x = ProcessData(std::move(d));
std::cout << "Test:" << x.id << std::endl;
return 0;
}
以下代码适用于所有版本的 gcc
,并且适用于具有相同编译器选项的更高版本的 icc。
它甚至适用于 -std=c++11 -O3
for icc 17.0
进一步调试发现正在生成的默认复制构造函数有问题。
我无法理解发生了什么问题,听说这是某种编译器错误,但在以后的版本中得到了解决?
解决方法
这是另一个关于何时发生 the fickle aggregate 的奇怪示例,这里还有 ICC 编译器(版本 17.0)中一个有争议的错误。
Data
是 C++14 和 C++17 中的聚合类(但不是 C++11)
在 C++11 中,根据 [dcl.init.aggr]/1 (N3337) [重点 我的]:
聚合是一个数组或一个类(子句 [class])没有用户提供的构造函数([class.ctor]),没有括号或相等的初始化器非静态数据成员([class.mem]),没有私有或受保护的非静态数据成员(条款[class.access]),没有基类(条款[class.mem])。派生]),并且没有虚函数([class.virtual])。
在 C++14 和 C++17 中,除了后者的一些 explicit
细节外,规则与 C++11 中的基本相同,只是要求不同
[...] 非静态数据成员没有大括号或等号初始化器
已被删除。
因此,由于您的类 Data
没有用户提供的构造函数(请参阅 [dcl.fct.def.default]/4),而且只有公共数据成员,因此它是 C++14 中的聚合和 C++17,而由于其默认成员初始值设定项,它不是 C++11 中的聚合。
ICC 错误地对 C++14 和 C++17 应用聚合初始化,其中 Data
是聚合
现在,按照direct-braced-init初始化
Data temp{std::forward<Data>(d)}
将枚举 C++11 中的构造函数重载,而在 C++14 和 C++17 中它是直接初始化,特别是通过以下规则(来自 cppreference:
T 类型对象的列表初始化的效果是:
- 如果 T 是一个聚合类并且初始值设定项列表具有相同或派生类型的单个元素(可能是 cv 限定的),则 对象从该元素初始化(通过复制初始化 复制列表初始化,或通过直接初始化 直接列表初始化)。
如果上述项目符号的条件不适用,则聚合类的 direct-brace-init 将是聚合初始化。这不应该在这里发生,但是 ICC 在 C++14 和 C++17 中似乎错误地应用了聚合初始化(其中 Data
是聚合类),使用 std::forward<Data>(d)
来直接初始化Data
的第一个数据成员(即id
类型的数据成员std::string
)而不是直接初始化Data
本身;所有其他数据成员都由它们关联的默认成员初始化器初始化。
用用户提供的构造函数替换显式默认的构造函数使聚合类成为非聚合类
[...] 在进一步调试时发现正在生成的默认复制构造函数有问题。
在您在这里展示的示例中,您只需提供您自己的复制构造函数,而与在其第一次声明时显式默认设置相比。这意味着类 Data
不再是聚合(在 C++11 到 C++17 中的任何一个中)。
C++20 梳理了大部分早期的聚合混淆
请注意,根据 C++20,根据 P1008R1 的实现(禁止使用用户声明的构造函数进行聚合),大多数聚合混淆已得到解决,特别是通过 no不再允许聚合具有用户声明构造函数,这是对类作为聚合的更严格要求,而不仅仅是禁止用户提供构造函数。