C++ 显式构造函数和嵌套初始值设定项列表
下面的代码使用大多数现代C++11兼容的编译器(GCC>=5.x、Clang、ICC、MSVC)成功编译 (编辑,谢谢@dyp)C++ 显式构造函数和嵌套初始值设定项列表,c++,c++11,overload-resolution,list-initialization,C++,C++11,Overload Resolution,List Initialization,下面的代码使用大多数现代C++11兼容的编译器(GCC>=5.x、Clang、ICC、MSVC)成功编译 (编辑,谢谢@dyp) 这是一个部分的回答和推测,解释我是如何解释发生的事情的,不是编译专家,而不是C++大师。 首先,我会用一些直觉和常识。显然,最后发生的事情是aB::B(a),因为这是b1唯一可用的构造函数(显然它不可能是aB::B(B&&),因为至少定义了一个副本构造函数,所以B::B(B&)不是为我们隐式定义的)。此外,A或B的第一个构造不能是A::A(const char*),因
这是一个部分的回答和推测,解释我是如何解释发生的事情的,不是编译专家,而不是C++大师。 首先,我会用一些直觉和常识。显然,最后发生的事情是a
B::B(a)
,因为这是b1唯一可用的构造函数(显然它不可能是aB::B(B&&)
,因为至少定义了一个副本构造函数,所以B::B(B&)
不是为我们隐式定义的)。此外,A或B的第一个构造不能是A::A(const char*)
,因为它是显式的,所以必须使用A::A(std::string)
。此外,最里面引用的文本是常量字符[5]
。所以我猜第一个最里面的构造是常量char*
;然后是字符串构造:std::string::string(const char*)
。还有一个花括号结构,我猜它是A::A(A&&&)
(或者可能是A::A(A&)
?)。所以,总结一下我的直觉猜测,结构的顺序应该是:
const char*
std::string
(实际上是std::basic_string
)c++filt
传递它以使其更具可读性)。以下是C++中特别提到的代码:
call 4006a0 <std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)@plt>
call 400858 <A::A(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)>
call 400868 <B::B(A)>
call 400680 <std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()@plt>
call 400690 <std::allocator<char>::~allocator()@plt>
call 400690 <std::allocator<char>::~allocator()@plt>
呼叫4006a0
致电400858
致电400868
打400680
致电400690
致电400690
因此,我们看到的适当可操作结构的顺序似乎是:
(没有看到1。)
2. <代码>标准::基本字符串::基本字符串(常量字符*/*忽略分配器*/)
3. <代码>A::A(标准::字符串)
4. <代码>B::B(A)
对于Clang5.0.0,结果与IIANM相似,至于MSVC,谁知道呢?也许是虫子?大家都知道,他们有时在一路正确支持语言标准方面有点狡猾。对不起,就像我说的-部分回答。b1({{{“test”}}})
类似于b1(A{std::string{const char*[1]{“test”}})代码>
16.3.3.1.5列表初始化顺序
否则,如果参数类型是字符数组133,并且初始值设定项列表中有一个元素是适当类型的字符串文字(11.6.2),则隐式转换序列是标识转换
编译器会尝试所有可能的隐式转换。例如,如果我们有具有以下构造函数的C类:
#include <string>
struct C
{
template<typename T, size_t N> C(const T* (&&) [N]) {}
template<typename T, size_t N> C(const T (&&) [N]) {}
template<typename T=char> C(const T* (&&)) {}
template<typename T=char> C(std::initializer_list<char>&&) {}
};
struct A
{
explicit A(const char *) {}
A(C ) {}
};
struct B
{
B(A) {}
B(B &) = delete;
};
int main( void )
{
const char* p{"test"};
const char p2[5]{"test"};
B b1({{{"test"}}});
}
我不想解释编译器的行为,而是想解释一下标准是怎么说的
主要示例
要从{{{“test”}}
直接初始化b1
,重载解析应用于选择B
的最佳构造函数。由于没有从{{{“test”}}
到B&
(列表初始值设定项不是左值)的隐式转换,因此构造函数B(B&)
不可行。然后我们关注构造函数B(A)
,并检查它是否可行
为了确定从{{“test”}}}
到A
的隐式转换序列(为了简单起见,我将使用符号{{“test”}}}
->A
),重载解析应用于选择A
的最佳构造函数,因此我们需要比较{“test”}
->和{{“test”}
->std::string
(注意大括号的最外层被省略),根据:
当对非聚合类类型T的对象进行列表初始化时,[dcl.init.list]指定根据本子条款中的规则执行重载解析,重载解析将分两个阶段选择构造函数:
- 最初,候选函数是类T的初始值设定项列表构造函数([dcl.init.list])
- 如果没有找到可行的初始值设定项列表构造函数,将再次执行重载解析,其中候选函数是类T的所有构造函数,参数列表由初始值设定项列表的元素组成
…在复制列表初始化中,如果选择了显式构造函数,则初始化格式不正确
注意:这里考虑所有构造函数,而不考虑说明符explicit
{{“test”}
->const char*
根据和不存在:
否则,如果参数类型不是类:
- 如果初始值设定项列表有一个本身不是初始值设定项列表的元素
- 如果初始值设定项列表没有元素
在除上述情况外的所有情况下,不可能进行转换
要确定{“test”}
->std::string
,将采用相同的过程,重载解析将选择std::string
的构造函数,该构造函数采用const char*
类型的参数
因此,{{{“test”}}
->a
是通过选择构造函数a(std::string)
来完成的
变化
如果删除了显式
,该怎么办?
这个过程没有改变。GCC将选择构造函数A(const char*)
,而Clang将选择构造函数A(std::string)
。我认为这是GCC的一个错误
如果b1
的初始值设定项中只有两层大括号怎么办?
注意{“test”}
->常量字符*
不存在,但{“test”}
->常量字符*
存在。因此如果
call 4006a0 <std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)@plt>
call 400858 <A::A(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)>
call 400868 <B::B(A)>
call 400680 <std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()@plt>
call 400690 <std::allocator<char>::~allocator()@plt>
call 400690 <std::allocator<char>::~allocator()@plt>
#include <string>
struct C
{
template<typename T, size_t N> C(const T* (&&) [N]) {}
template<typename T, size_t N> C(const T (&&) [N]) {}
template<typename T=char> C(const T* (&&)) {}
template<typename T=char> C(std::initializer_list<char>&&) {}
};
struct A
{
explicit A(const char *) {}
A(C ) {}
};
struct B
{
B(A) {}
B(B &) = delete;
};
int main( void )
{
const char* p{"test"};
const char p2[5]{"test"};
B b1({{{"test"}}});
}
29 : <source>:29:11: error: call to constructor of 'C' is ambiguous
B b1({{{"test"}}});
^~~~~~~~~~
5 : <source>:5:40: note: candidate constructor [with T = char, N = 1]
template<typename T, size_t N> C(const T* (&&) [N]) {}
^
6 : <source>:6:40: note: candidate constructor [with T = const char *, N = 1]
template<typename T, size_t N> C(const T (&&) [N]) {}
^
7 : <source>:7:39: note: candidate constructor [with T = char]
template<typename T=char> C(const T* (&&)) {}
^
15 : <source>:15:9: note: passing argument to parameter here
A(C ) {}
^
29 : <source>(29): error C2664: 'B::B(B &)': cannot convert argument 1 from 'initializer list' to 'A'