大括号初始化的向量构造函数参数在 gcc 中引发歧义错误,但在 clang 中不会,并且取决于向量的大小



我有一个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有一个构造函数用于任何类型的U1U2std::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的了解和我对正在发生的事情的推断的结合。对这个答案持保留态度,可能会发生什么。

相关内容

  • 没有找到相关文章

最新更新