我有一个C++类,它接受 std::p air<std::string:string> 的 std::vector 作为其参数。 我发现当我使用大括号初始值设定项列表调用构造函数时,我从 gcc 收到一个模棱两可的构造函数错误,但前提是向量只包含一个元素。 如果向量包含两个元素,则不会发生错误。 此外,即使向量只包含一个元素,clang 也可以编译程序! 我尝试了从 5.x 到 10.x 的 gcc 版本,并从 10.x 到 12.x 尝试了 clang 版本。 我的最小复制案例如下所示(用g++ -lstdc++ -std=c++14
编译):
#include <utility>
#include <vector>
#include <string>
class Foo {
public:
explicit Foo(const std::vector< std::pair<std::string, std::string> >& list) {
(void)list;
}
Foo(const Foo& other)
{
(void)other;
}
};
int main() {
Foo f = Foo({{"a", "b"}, {"c", "d"}}); // Compiles with clang and gcc.
Foo g = Foo({{"a", "b"}}); // Compiles with clang, but not gcc.
(void)f;
(void)g;
}
Clang编译它没有错误,但是使用gcc-10.3(例如),我得到:
<source>: In function 'int main()':
<source>:19:29: error: call of overloaded 'Foo(<brace-enclosed initializer list>)' is ambiguous
19 | Foo g = Foo({{"a", "b"}}); // Compiles with clang, but not gcc.
| ^
<source>:11:3: note: candidate: 'Foo::Foo(const Foo&)'
11 | Foo(const Foo& other)
| ^~~
<source>:7:12: note: candidate: 'Foo::Foo(const std::vector<std::pair<std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> > >&)'
7 | explicit Foo(const std::vector< std::pair<std::string, std::string> >& list) {
| ^~~
有谁知道为什么这在 gcc 中不起作用? 我本以为explicit
关键字足以消除歧义,当向量具有多个元素时,它起作用真的很奇怪。 提前感谢您的任何指导!
考虑以下程序:
#include <iostream>
#include <string>
class Foo {
public:
explicit Foo(const std::string& str)
{
std::cout << str;
}
};
int main() {
Foo f({ "a", "b", });
}
MSVC和gcc将在没有任何警告或错误的情况下编译它,但是当您尝试运行它时,程序会崩溃。
让我们试着弄清楚发生了什么。
std::string
有许多构造函数,请参阅 https://en.cppreference.com/w/cpp/string/basic_string/basic_string
其中一个允许我们做一些漂亮的事情,如下所示:
std::string a("hello");
std::string b(a.begin(), a.end());
不幸的是,同一个构造函数可能会被滥用,造成如下所示的混乱:
std::string c("a", "b");
代码的问题在于编译器不知道如何解释初始化列表。
当您只有{"a", "b"}
时,它可以是std::string
或一对值的构造函数。
你能做的最好的事情就是尽量不要混淆编译器,而是使用std::make_pair
或使用正确的括号:
Foo f = Foo({std::make_pair("a", "b"), std::make_pair("c", "d")});
Foo g = Foo({std::make_pair("a", "b")});
这是我的解释尝试:
这一行:
Foo f = Foo({{"a", "b"}, {"c", "d"}});
大致相当于以下内容:
Foo f =
Foo(
std::initializer_list<std::initializer_list<const char*>>{
std::initializer<const char*>{"a", "b"},
std::initializer<const char*>{"c", "d"}
}
);
由于std::pair
有一个构造函数用于任何类型的U1
和U2
,std::pair
可以从一个initializer_list<const char*>
或两个const char*
构造。编译器很可能构造了两个std::pair<std::string, std::string>
,因为它看到我们对上述类型的std::vector
有重载:
Foo f =
Foo(
std::initializer_list<std::pair<std::string, std::string>>{
std::pair<std::string, std::string>("a", "b"),
std::pair<std::string, std::string>("c", "d")
}
);
由于std::vector<T>
有一个接受std::initializer_list<T>
的构造函数,因此选择了正确的重载。
我们来看看这个:
Foo g = Foo({{"a", "b"}});
这变成了:
Foo f =
Foo(
std::initializer_list<std::initializer_list<const char*>>{
std::initializer_list<const char*>{"a", "b"}
}
);
在这里,我们在做什么并不那么明显。编译器(假设我们只是白痴)认为我们可能正在尝试从initializer_list<const char*>
创建一个Foo
然后复制该Foo
,或者我们直接创建带有initilizer_list<initializer_list<const char*>>
的Foo
。由于std::vector<std::pair<std::string,std::string>>
的构造函数采用std::initializer_list<std::pair<std::string,std::string>>
而不是std::initializer_list<std::initializer_list<const char*>>
,因此愚蠢的编译器不清楚我们要做什么。
这太狂野了!但是,请考虑如果 foo copy 构造函数标记为explicit
会发生什么:
Foo f = Foo({{"a", "b"}, {"c", "d"}}); // Compiles with clang and gcc.
// Foo g = Foo({{"a", "b"}});
Foo f2 = Foo({f}); // No errors! Equivalent to Foo{f}
由于Foo({f})
等价于Foo{f}
,则g可以认为
Foo g = Foo{{"a", "b"}};
编译器不会对 f 执行相同的操作,因为没有 foo 构造函数接受两个参数(即{"a","b"}
和{"c","d"}
)。
此外,请考虑从"a"和"b"显式构造std::string
时会发生什么:
Foo g = Foo({{std::string("a"), std::string("b")}});
这运行良好。这是因为编译器可以清楚地看到std::vector<std::pair<std::string, std::string>>
是比Foo
更好的选择,因为std::p air可以从两个std::string
构造而成。
结论:两件事之一最有可能为真:
- 海湾合作委员会只是对我们试图做什么感到困惑,正如它所看到的那样。
Foo(const Foo&)
和Foo(const std::vector<std::pair<std::string,std::string>>&)
都拿一个 论点,两者似乎同样有效。 - 由于
Foo({...})
等价于Foo{...}
,编译器对我们调用Foo{{"a","b"}}
的事实感到困惑,试图从"a"和"b"创建一个std::vector<std::pair<std::string,std::string>>
。
解决方案:
- 请改用
Foo{{{"a", "b"}}}
;或者: - 将
using namspace std::string_literals
放入代码中,并将Foo({{"a", "b"}})
替换为Foo({{"a"s, "b"s}})
;或者: - 使用
std::make_pair
编辑:更重要的一点:如果您删除Foo复制构造函数,很明显编译器正在尝试从"a"创建一对,从"b"创建另一对。查看此代码的错误消息:
#include <iostream>
#include <utility>
#include <vector>
#include <string>
class Foo {
public:
Foo(const std::vector< std::pair<std::string, std::string> >& list) {}
Foo(const Foo&) = delete;
};
int main() {
Foo foo1({ {"a", "b"}, {"c", "d"} });
Foo foo2({ {"a", "b"} });
}
错误:调用"std::p air"没有匹配函数, std::__cxx11::basic_string>::p air(const char&)'
请注意:这只是我对std::initializer_list
的了解和我对正在发生的事情的推断的结合。对这个答案持保留态度,可能会发生什么。