为什么使用统一的初始化语法会导致与 "old" 样式 () 不同的行为?
Why does using uniform initializer syntax result in different behavior to the "old" style ()?
如果我尝试对 std::set
使用统一的初始值设定项,我会得到不同的结果。
示例:
int main()
{
std::array a {1,2,3,4};
std::set<int> s1 {a.begin(), a.end()};
std::set s2 {a.begin(), a.end()};
std::set s3 (a.begin(), a.end());
for(auto& i: s1) { std::cout << i << "\n"; }
std::cout << "####" << std::endl;
for(auto& i: s2) { std::cout << i << "\n"; }
std::cout << "####" << std::endl;
for(auto& i: s3) { std::cout << i << "\n"; }
}
结果:
1
2
3
4
####
0x7ffecf9d12e0
0x7ffecf9d12f0
####
1
2
3
4
这似乎与 "deduction guides" 有关,如果与 {}
或 ()
语法一起使用,则评估不同。
简答
对于s2
,使用大括号语法,{a.begin(), a.end()}
被认为是std::array<int>::iterator
的initializer_list
。因此,s2
是一组迭代器。
对于s3
,使用括号语法,选择迭代器构造函数。 s3
是 int
的集合,从 [a.begin(), a.end())
.
范围初始化
长答案
根据 [set.overview],我们有两个相关的推导指南:
template<class InputIterator,
class Compare = less<typename iterator_traits<InputIterator>::value_type>,
class Allocator = allocator<typename iterator_traits<InputIterator>::value_type>>
set(InputIterator, InputIterator,
Compare = Compare(), Allocator = Allocator())
-> set<typename iterator_traits<InputIterator>::value_type, Compare, Allocator>;
和
template<class Key, class Compare = less<Key>, class Allocator = allocator<Key>>
set(initializer_list<Key>, Compare = Compare(), Allocator = Allocator())
-> set<Key, Compare, Allocator>;
根据 [over.match.class.deduct]/1:
A set of functions and function templates is formed comprising:
[...]
(1.4) For each deduction-guide, a function or function template with the following properties:
The template parameters, if any, and function parameters are those of the deduction-guide.
The return type is the simple-template-id of the deduction-guide.
本例中,上述推导指南的合成函数和函数模板分别为
template<class InputIterator,
class Compare = less<typename iterator_traits<InputIterator>::value_type>,
class Allocator = allocator<typename iterator_traits<InputIterator>::value_type>>
auto __func1(InputIterator, InputIterator,
Compare = Compare(), Allocator = Allocator())
-> set<typename iterator_traits<InputIterator>::value_type, Compare, Allocator>;
和
template<class Key, class Compare = less<Key>, class Allocator = allocator<Key>>
auto __func2(initializer_list<Key>, Compare = Compare(), Allocator = Allocator())
-> set<Key, Compare, Allocator>;
(我用双下划线表示这些名字是合成的,不能用其他方式访问。)
根据 [over.match.class.deduct]/2:
Initialization and overload resolution are performed as described in
[dcl.init] and [over.match.ctor], [over.match.copy], or
[over.match.list] (as appropriate for the type of initialization
performed) for an object of a hypothetical class type, where the
selected functions and function templates are considered to be the
constructors of that class type for the purpose of forming an overload
set, and the initializer is provided by the context in which class
template argument deduction was performed. Each such notional
constructor is considered to be explicit if the function or function
template was generated from a constructor or deduction-guide that
was declared explicit
. All such notional constructors are considered
to be public members of the hypothetical class type.
假设的 class 类型如下所示:
class __hypothetical {
public:
// ...
// #1
template<class InputIterator,
class Compare = less<typename iterator_traits<InputIterator>::value_type>,
class Allocator = allocator<typename iterator_traits<InputIterator>::value_type>>
__hypothetical(InputIterator, InputIterator,
Compare = Compare(), Allocator = Allocator())
-> set<typename iterator_traits<InputIterator>::value_type, Compare, Allocator>;
// #2
template<class Key, class Compare = less<Key>, class Allocator = allocator<Key>>
__hypothetical(initializer_list<Key>, Compare = Compare(), Allocator = Allocator())
-> set<Key, Compare, Allocator>;
// ...
};
对于s2
,
的声明
std::set s2 {a.begin(), a.end()};
重载解析如同在
中一样执行
__hypothetical __hyp{a.begin(), a.end()}; // braces
因此,[over.match.list]进来了。
[...] overload resolution selects the constructor in two phases:
Initially, the candidate functions are the initializer-list constructors ([dcl.init.list]) of the class T
and the argument list
consists of the initializer list as a single argument.
[...]
构造函数 #2 是一个 initializer-list 构造函数。函数模板参数推导给出
Key = std::array<int>::iterator
所以s2
的推导类型是
std::set<std::array<int>::iterator>
s2
的声明等同于
std::set<std::array<int>::iterator> s2 {a.begin(), a.end()};
因此,s2
是一组迭代器,由两个元素组成:a.begin()
和a.end()
。在您的情况下,std::array<int>::iterator
可能是 int*
,而 a.begin()
和 a.end()
恰好分别被序列化为 0x7ffecf9d12e0
和 0x7ffecf9d12f0
。
对于s3
,重载决议的执行就好像在
中一样
__hypothetical __hyp(a.begin(), a.end()); // parentheses
即direct-initialization,属于[pver.match.ctor]的范畴。 initializer_list
构造函数无关紧要,而是选择构造函数 #1。函数模板参数推导给出
InputIterator = std::array<int>::iterator
所以s3
的推导类型是
set<iterator_traits<std::array<int>::iterator>::value_type>
也就是 set<int>
。因此,s3
的声明等同于
std::set<int> s3 (a.begin(), a.end());
s3
是一组 int
,从范围 [a.begin(), a.end())
初始化 — 四个元素 1, 2, 3, 4
,这解释了输出。
如果我尝试对 std::set
使用统一的初始值设定项,我会得到不同的结果。
示例:
int main()
{
std::array a {1,2,3,4};
std::set<int> s1 {a.begin(), a.end()};
std::set s2 {a.begin(), a.end()};
std::set s3 (a.begin(), a.end());
for(auto& i: s1) { std::cout << i << "\n"; }
std::cout << "####" << std::endl;
for(auto& i: s2) { std::cout << i << "\n"; }
std::cout << "####" << std::endl;
for(auto& i: s3) { std::cout << i << "\n"; }
}
结果:
1
2
3
4
####
0x7ffecf9d12e0
0x7ffecf9d12f0
####
1
2
3
4
这似乎与 "deduction guides" 有关,如果与 {}
或 ()
语法一起使用,则评估不同。
简答
对于s2
,使用大括号语法,{a.begin(), a.end()}
被认为是std::array<int>::iterator
的initializer_list
。因此,s2
是一组迭代器。
对于s3
,使用括号语法,选择迭代器构造函数。 s3
是 int
的集合,从 [a.begin(), a.end())
.
长答案
根据 [set.overview],我们有两个相关的推导指南:
template<class InputIterator, class Compare = less<typename iterator_traits<InputIterator>::value_type>, class Allocator = allocator<typename iterator_traits<InputIterator>::value_type>> set(InputIterator, InputIterator, Compare = Compare(), Allocator = Allocator()) -> set<typename iterator_traits<InputIterator>::value_type, Compare, Allocator>;
和
template<class Key, class Compare = less<Key>, class Allocator = allocator<Key>> set(initializer_list<Key>, Compare = Compare(), Allocator = Allocator()) -> set<Key, Compare, Allocator>;
根据 [over.match.class.deduct]/1:
A set of functions and function templates is formed comprising:
[...]
(1.4) For each deduction-guide, a function or function template with the following properties:
The template parameters, if any, and function parameters are those of the deduction-guide.
The return type is the simple-template-id of the deduction-guide.
本例中,上述推导指南的合成函数和函数模板分别为
template<class InputIterator,
class Compare = less<typename iterator_traits<InputIterator>::value_type>,
class Allocator = allocator<typename iterator_traits<InputIterator>::value_type>>
auto __func1(InputIterator, InputIterator,
Compare = Compare(), Allocator = Allocator())
-> set<typename iterator_traits<InputIterator>::value_type, Compare, Allocator>;
和
template<class Key, class Compare = less<Key>, class Allocator = allocator<Key>>
auto __func2(initializer_list<Key>, Compare = Compare(), Allocator = Allocator())
-> set<Key, Compare, Allocator>;
(我用双下划线表示这些名字是合成的,不能用其他方式访问。)
根据 [over.match.class.deduct]/2:
Initialization and overload resolution are performed as described in [dcl.init] and [over.match.ctor], [over.match.copy], or [over.match.list] (as appropriate for the type of initialization performed) for an object of a hypothetical class type, where the selected functions and function templates are considered to be the constructors of that class type for the purpose of forming an overload set, and the initializer is provided by the context in which class template argument deduction was performed. Each such notional constructor is considered to be explicit if the function or function template was generated from a constructor or deduction-guide that was declared
explicit
. All such notional constructors are considered to be public members of the hypothetical class type.
假设的 class 类型如下所示:
class __hypothetical {
public:
// ...
// #1
template<class InputIterator,
class Compare = less<typename iterator_traits<InputIterator>::value_type>,
class Allocator = allocator<typename iterator_traits<InputIterator>::value_type>>
__hypothetical(InputIterator, InputIterator,
Compare = Compare(), Allocator = Allocator())
-> set<typename iterator_traits<InputIterator>::value_type, Compare, Allocator>;
// #2
template<class Key, class Compare = less<Key>, class Allocator = allocator<Key>>
__hypothetical(initializer_list<Key>, Compare = Compare(), Allocator = Allocator())
-> set<Key, Compare, Allocator>;
// ...
};
对于s2
,
std::set s2 {a.begin(), a.end()};
重载解析如同在
中一样执行__hypothetical __hyp{a.begin(), a.end()}; // braces
因此,[over.match.list]进来了。
[...] overload resolution selects the constructor in two phases:
Initially, the candidate functions are the initializer-list constructors ([dcl.init.list]) of the class
T
and the argument list consists of the initializer list as a single argument.[...]
构造函数 #2 是一个 initializer-list 构造函数。函数模板参数推导给出
Key = std::array<int>::iterator
所以s2
的推导类型是
std::set<std::array<int>::iterator>
s2
的声明等同于
std::set<std::array<int>::iterator> s2 {a.begin(), a.end()};
因此,s2
是一组迭代器,由两个元素组成:a.begin()
和a.end()
。在您的情况下,std::array<int>::iterator
可能是 int*
,而 a.begin()
和 a.end()
恰好分别被序列化为 0x7ffecf9d12e0
和 0x7ffecf9d12f0
。
对于s3
,重载决议的执行就好像在
__hypothetical __hyp(a.begin(), a.end()); // parentheses
即direct-initialization,属于[pver.match.ctor]的范畴。 initializer_list
构造函数无关紧要,而是选择构造函数 #1。函数模板参数推导给出
InputIterator = std::array<int>::iterator
所以s3
的推导类型是
set<iterator_traits<std::array<int>::iterator>::value_type>
也就是 set<int>
。因此,s3
的声明等同于
std::set<int> s3 (a.begin(), a.end());
s3
是一组 int
,从范围 [a.begin(), a.end())
初始化 — 四个元素 1, 2, 3, 4
,这解释了输出。