C++:尽管有显式构造函数,但大括号初始化

C++: Curly Braces Initialization Despite Explicit Constructor

提问人:AnonA 提问时间:8/23/2023 最后编辑:AnonA 更新时间:8/23/2023 访问量:57

问:

我有一个数据类(就像 C 意义上的结构:一个只有一堆属性的类),我用来用大括号初始化初始值设定项列表(我认为是通过隐式复制构造函数),如下所示:

ClassName inst = {1, 2, 3, 4};

然后我决定要从向量启用初始化,所以我定义了一个看起来像这样的 ctor:

ClassName(std::vector<int> vec) : A(vec[0]), B(vec[1]), C(vec[2]), D(vec[3]) {}

但是,自从添加此构造函数以来,由于“无法从'初始值设定项列表'转换为'namespace::ClassName'”,上面引用的大括号初始化方法不再有效。

我尝试在谷歌上搜索解释/建议/解决方案,但找不到正确的关键字,而且我似乎没有找到任何确切的答案。

您如何建议解决这个问题(无论是具体如何重新启用此类列表初始化,还是在设计方面,如果我应该以某种方式避免这种情况)?

C++ 构造函数 初始化 initializer-list

评论

1赞 Jan Schultke 8/23/2023
为什么要通过 for 恰好有四个成员的聚合类型来启用初始化?也许在静态范围内启用它是有意义的,但这只是在概念上是错误的。如果你真的需要它,你可以做一个免费的函数来做它,并继续使用聚合初始化。std::vectorstd::span
0赞 AnonA 8/24/2023
@JanSchultke,我的实际用例涉及解析在长管道之后创建的向量,我需要将其解析为这样的聚合类型。聚合类型总是被期望从向量中初始化出来,而不是以任何其他方式初始化(初始化器列表只是在我测试它时的一种方便,这个问题主要是出于好奇)。我是否正确理解您使用向量描述的概念问题是向量的大小是动态的,并且有更适合固定大小情况的 std 容器?
0赞 AnonA 8/24/2023
不过,基本上:最主要的是我想要一个“命名向量”;有点像字典,但具有在编译时已知的键,并用作 API 的一部分。它的内容是在运行一个创建向量的长管道后获得的,虽然理论上我可以将其从向量转换为数组,但是有几个这样的类有几种不同的大小,所以我认为“涵盖所有情况”的简单方法(以便使实际管道尽可能通用)是输出一个向量, 从而消耗带有聚合的向量。感谢您的想法!:)
0赞 Jan Schultke 8/24/2023
是的,概念问题是您正在将具有静态范围的范围初始化为具有动态范围的范围。至少,这不应该属于构造函数。无论负责获取向量然后从中创建 s 的函数都应该进行从动态到静态范围的转换。您可能还想使用 .您可以从具有动态范围的跨度显式转换为具有静态范围的跨度,这在概念上是执行您正在执行的操作,但您将保持构造函数干净。ClassNamestd::span
0赞 AnonA 8/24/2023
非常感谢您的输入,@JanSchultke;非常感谢!

答:

0赞 pptaszni 8/23/2023 #1

添加用户定义的构造函数后,您的类不再是聚合类型(假设它以前是聚合类型,因为您的问题不完整),因此列表初始化的最后一条规则适用:

否则,将分两个阶段考虑 T 的构造函数:

(...)

如果上一阶段未产生匹配项,则 T 的所有构造函数都参与针对由 braced-init-list 的元素组成的参数集的重载解析,但限制是只允许非缩小转换。(...).

因此,编译器尝试传递 4 个类型的参数,而您的构造函数需要 1 个类型的参数。如果您改为像这样初始化实例intstd::vector<int>

ClassName inst = {{1, 2, 3, 4}};

编译器将选择预期的 1 参数构造函数,然后转换为使用其构造函数,并采用 .std::vector<int>{1, 2, 3, 4}std::vector<int>initializer_list

评论

0赞 AnonA 8/23/2023
非常感谢 - “聚合初始化”是我缺少的关键字。尽管存在我的显式构造函数,但有没有办法启用这种默认的初始值设定项列表构造函数,或者我是否必须采用您建议的嵌套大括号/向量初始化方法?
0赞 pptaszni 8/23/2023
是的,您可以添加另一个 ctor 获取和委托给构造函数获取向量:godbolt.org/z/cMj3rT67E - 然后是我 (...-out 将适用。initializer_list
1赞 AnonA 8/23/2023
我标记了@4463035818_is_not_an_ai的答案,因为它也恰好包含了对我的这个问题的答案,但您的答案也非常有帮助——非常感谢您抽出时间接受采访。希望我能标记两个答案!
3赞 463035818_is_not_an_ai 8/23/2023 #2

请考虑以下三个类:

#include <vector>
#include <initializer_list>

struct aggregate {
    int a,b,c,d;
};
struct non_aggregate {
    int a,b,c,d;
    non_aggregate(std::vector<int>) {}
};
struct init_list_constr {
    int a,b,c,d;
    init_list_constr(std::initializer_list<int>) {}
};

int main() {
    aggregate a = {1,2,3,4};
    //non_aggregate b = {1,2,3,4}; // error
    init_list_constr c = {1,2,3,4};
}

aggregate是一个聚合,因为它没有用户定义的构造函数。您可以使用聚合初始化: 。 不是聚合,因为它具有用户定义的构造函数,并且不能使用聚合初始化。aggregate a = {1,2,3,4};non_aggregate

我建议你提供一个构造函数,它也可以使用用户定义的构造函数进行聚合初始化。std::initializer_list

有关详细信息,我建议您 https://en.cppreference.com/w/cpp/language/aggregate_initialization

评论

0赞 AnonA 8/23/2023
非常感谢——这非常有帮助,这正是我想要的。:)
0赞 463035818_is_not_an_ai 8/23/2023
@AnonA请注意,我的代码只是一个玩具示例。初始值设定项 lsit 构造函数只允许您编写代码,但您仍然需要编写代码来初始化成员。init_list_constr c = {1,2,3,4};