为什么编译器总是选择非显式构造函数来进行复制列表初始化?

Kis*_*mar 3 c++ copy-constructor language-lawyer c++11

对于下面的代码,编译器是否有任何原因会选择非显式构造函数。

struct S {
    S() = default;
    explicit S(S & cp) {
      std::cout << "explicit" << std::endl;
    } 
    S(const S & cp) {
      std::cout << "copy" << std::endl;
    };
};


int main() {  
    S s1; 
    S s2 = {s1}; 
}
Run Code Online (Sandbox Code Playgroud)

来自https://en.cppreference.com/w/cpp/language/list_initialization,它说对于copy-list-ini...显式和非显式构造函数都被考虑,但只能调用非显式构造函数

https://godbolt.org/z/YqWWs9jqY

dfr*_*fri 7

程序格式不规范,但存在实施分歧

从CWG 2137开始,您的程序应该是格式错误的。然而,这里存在实现分歧,其中 Clang 尚未实现 CWG 2137,而 GCC 已实现 CWG 2137 的大部分部分,但可能会缺少上下文复制列表初始化中显式构造函数的情况(具有相同的单个元素)班级)。

错误报告(我刚刚发布了 GCC 报告):

规则

[dcl.init.list]管理列表初始化的规则,以及以下内容:

S s2 = {s1}; 
Run Code Online (Sandbox Code Playgroud)

自 CWG 2137 起,[dcl.init.list]/3.7适用:

否则,如果 T 是类类型,则考虑构造函数。枚举适用的构造函数,并通过重载决策([over.match]、[over.match.list])选择最好的构造函数。如果需要缩小转换(见下文)来转换任何参数,则该程序格式错误。

由于没有初始化列表构造函数,因此[over.match.list]/1.2适用:

/1.2 否则,或者如果没有找到可行的初始值设定项列表构造函数,则再次执行重载决策,其中候选函数是类 T 的所有构造函数,参数列表由初始值设定项列表的元素组成。

在复制列表初始化中,如果选择显式构造函数,则初始化格式错误。

由于显式构造函数是更好的匹配,因此应该在程序格式错误之后选择它,因为 [over.match.list]/1 不允许在复制列表初始化中使用显式构造函数。

GCC 和 Clang 都错误地接受了该计划,但 Clang 由于尚未实现 CWG 2137,而 GCC 由于缺少 CWG 2137 引入的一些边缘情况,因此情况稍差一些。