【问题标题】:Resolution of function overloading about initializer_list关于 initializer_list 函数重载的解决方法
【发布时间】:2019-04-08 14:41:08
【问题描述】:
#include <vector>
using namespace std;

class A
{
public:
    explicit A(const initializer_list<int> & a) {}
};
void func(const vector<int>& a)
{

}
void func(A a)
{

}
int main(void)
{
    func({ 1,2,3 });
}

这段代码编译失败:

(19): 错误 C2668: 'func': 对重载函数的模糊调用

(13): 注意:可能是 'void func(A)'

(9): 注意: or 'void func(const std::vector> &)' 与[_Ty=int] (19): 注意:在尝试匹配参数列表时 '(initializer list)'

请注意,我在 A 的构造函数中指定了“显式”。

在我看来,func(A a) 不应被视为{1,2,3} 的候选者。事实上,事实并非如此。如果我删除func(const vector&lt;int&gt;&amp; a),那么代码仍然会失败,而不是通过删除歧义来成功。

总而言之,在这段代码中,func(const vector&lt;int&gt;&amp; a){1,2,3} 的唯一可调用函数,因此没有歧义。

我的问题是..

  1. C++ 重载解析过程如何得出“模糊”结论?

  2. 为什么 C++ 不直接选择可调用的?

【问题讨论】:

  • 这用clang编译得很好:godbolt.org/z/tr8Otk
  • @Holt 在 GCC 上休息:godbolt.org/z/UMyaKj
  • 哦..我明白了。但我的 Visual Studio 2017 使用 C2668 显示“失败”
  • 请从输出选项卡复制错误的整个文本。我的意思是输出选项卡。错误列表具有缩写格式,不适合复制/粘贴。
  • 这是完整的错误消息。 (21): 错误 C2668: 'func': 对重载函数的模糊调用 (15): 注意: 可能是 'void func(A)' (11): 注意: 或 'void func(const std::vector> &)' 1> with 1> [ 1> _Ty=int 1> ] (21): 注意:在尝试匹配参数列表时 '(initializer list)'

标签: c++ c++11


【解决方案1】:

explicit 构造函数在执行列表初始化时不会被忽略。这样的构造函数总是被认为是可行的重载候选者。发生的情况是,如果系统尝试在 copy-list-initialization 下(即:在重载解析之后)调用 explicit 构造函数,则会出现硬编译错误。

在你的情况下,它永远不会走那么远,因为重载集是模棱两可的。

explicit 并不意味着“如果你尝试转换就不存在”;它的意思是“如果你尝试转换就会出错”。 explicit 的重点是迫使用户思考他们真正想要使用的类型。它可以防止用户编写对读者来说有些模糊的代码。

【讨论】:

【解决方案2】:

我相信 clang 在这里是正确的。 C++ 中的重载解析分三个阶段进行:首先构建一组候选函数,这是调用可能潜在引用的所有函数的集合(基本上,名称解析提取的所有函数的集合)。然后缩小这个初始的候选函数集以得到一组可行的函数(可以使用给定参数进行调用的函数集)。最后,对可行函数进行排序以确定最佳可行函数。最终将调用这个最可行的函数。

来自[over.match.viable]/4

第三,为了使F 成为一个可行的函数,每个参数都应该存在一个隐式转换序列,该序列将该参数转换为F 的相应参数。 […]

基于[over.best.ics]/6,特别是

当参数类型不是引用时,隐式转换序列模拟参数表达式中参数的复制初始化。 […]

似乎void func(A a) 没有这样的隐式转换序列,因为必要的构造函数被标记为explicit(复制初始化会失败)。因此,该函数不是一个可行的函数,并且不再考虑用于重载解决方案,这使得 void func(const vector&lt;int&gt;&amp; a) 作为唯一可行的候选者,这是随后将被调用的函数。

此外,纯粹在概念层面上,只有在我们真正知道要初始化哪个参数(即知道哪个函数实际上将被调用。如果在每个潜在候选函数中存在一个参数不是相应参数的有效初始值设定项时,对重载集的调用是非法的,那么重载的意义何在?只要我们仍在努力确定要调用哪个函数,就无法确定初始化是否格式错误。 clang 正是表现出这种行为。当你注释掉 void func(const std::vector&lt;int&gt;&amp; a) 重载时,clang 会突然抱怨调用格式不正确……

try it out here

【讨论】:

  • 那么,gcc和mcvc错了吗?我试图理解你和波拉斯的答案,但很难下结论..
  • @i.stav 我认为他们错了。我上面的帖子解释了我为什么相信这一点的理由。毕竟,可能是我错了。我不得不承认,我发现在这个特定问题上的标准措辞有点含糊。如果您也将 icc 加入其中,那么关于哪个是正确的就是 2 : 2...
【解决方案3】:

我同意@Nicol Bolas 的观点。 MSVC 和 gcc 是对的,而 clang 和 icc 是错的。

在重载决议中,列表初始化不同于复制初始化,列表初始化考虑显式构造函数,而复制初始化不考虑。

(来自cppreference

List-initialization 当一个非聚合类类型 T 的对象是 发生列表初始化的两阶段重载决议。

在阶段 1,候选函数都是初始化列表 T 的构造函数和用于重载的参数列表 如果重载,则解析由单个初始化列表参数组成 解决在第 1 阶段失败,进入第 2 阶段,其中候选人 函数都是 T 的构造函数和 重载决议的目的由各个元素组成 初始化列表。如果初始化列表为空并且 T 有一个 默认构造函数,第 1 阶段被跳过。

在复制列表初始化中,如果第 2 阶段选择显式 构造函数,初始化格式不正确(而不是全部 没有显式构造函数的复制初始化 考虑)。

一些例子:

这个

#include <iostream>
#include <initializer_list>

struct A
{
    explicit A(int, int, int) {}
};

struct B
{
    B(std::initializer_list<int>) {}
};

void f(A)   //f1
{
    std::cout << 1 << std::endl;
}

void f(B)   //f2
{
    std::cout << 2 << std::endl;
}

int main()
{
    f({ 1,2,3 });  //list initialziation
}

在 MSVC 和 gcc 上失败。 (见herehere

这个

#include <iostream>
#include <initializer_list>

struct A
{
    explicit A(std::initializer_list<int>) {}
};

struct B
{
    B(std::initializer_list<int>) {}
};

void f(A)   //f1
{
    std::cout << 1 << std::endl;
}

void f(B)   //f2
{
    std::cout << 2 << std::endl;
}

int main()
{
    f({ 1,2,3 });  //Also list initialization
}

在 MSVC 和 gcc 上也失败了。 (见herehere

虽然这个

#include <iostream>
#include <initializer_list>

struct A
{
    explicit A(int) {}
};

struct B
{
    B(int) {}
};

void f(A)   //f1
{
    std::cout << 1 << std::endl;
}

void f(B)   //f2
{
    std::cout << 2 << std::endl;
}

int main()
{
    f(1);  //Copy initialization
}

在所有四个编译器上都成功。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-06-22
    • 1970-01-01
    • 1970-01-01
    • 2019-04-09
    • 2014-08-30
    • 1970-01-01
    • 2019-10-11
    • 2011-01-24
    相关资源
    最近更新 更多