为什么使用统一的初始化程序语法会导致行为与“旧”样式()不同?

Kla*_*aus 7 c++ uniform-initialization c++17

如果尝试对进行统一的初始化,则会得到不同的结果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"; }
}
Run Code Online (Sandbox Code Playgroud)

结果是:

1   
2   
3   
4   
####
0x7ffecf9d12e0
0x7ffecf9d12f0
####
1   
2   
3   
4  
Run Code Online (Sandbox Code Playgroud)

这似乎与“演绎指南”有关,如果与{}()语法一起使用,则对它们的评估会有所不同。

L. *_* F. 7

简短答案

s2,支架语法被使用,并且{a.begin(), a.end()}被认为是一个initializer_liststd::array<int>::iterator第 因此,s2是一组迭代器。

对于s3,使用括号语法,并选择迭代器构造函数。s3是的集合int,并从range初始化[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>;
Run Code Online (Sandbox Code Playgroud)

template<class Key, class Compare = less<Key>, class Allocator = allocator<Key>>
  set(initializer_list<Key>, Compare = Compare(), Allocator = Allocator())
    -> set<Key, Compare, Allocator>;                                                
Run Code Online (Sandbox Code Playgroud)

[over.match.class.deduct] / 1

形成一组功能和功能模板,包括:

  • [...]

  • (1.4)对于每个推导,具有以下属性的函数或函数模板:

    • 模板参数(如果有)和功能参数是推导指南中的参数

    • 返回类型是简单模板id的的扣除引导

在这种情况下,上述推导的综合功能和功能模板分别为

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>;
Run Code Online (Sandbox Code Playgroud)

template<class Key, class Compare = less<Key>, class Allocator = allocator<Key>>
auto __func2(initializer_list<Key>, Compare = Compare(), Allocator = Allocator())
  -> set<Key, Compare, Allocator>; 
Run Code Online (Sandbox Code Playgroud)

(我用双下划线表示这些名称是综合名称,否则无法访问。)


[over.match.class.deduct] / 2

初始化和重载解析按照[dcl.init]和[over.match.ctor],[over.match.copy]或[over.match.list](适用于所执行的初始化类型)中的描述进行。假设类类型的对象,其中选定的函数和函数模板被认为是该类类型的构造函数,以形成重载集,并且初始化器由执行类模板参数推导的上下文提供。每个这样的名义构造被认为是如果从一个构造函数或生成的函数或函数模板是明确的扣除引导被声明explicit。所有这些概念构造器均被视为假设类类型的公共成员。

假设的类类型如下:

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>;

  // ...
};
Run Code Online (Sandbox Code Playgroud)

对于的声明s2

std::set s2 {a.begin(), a.end()};
Run Code Online (Sandbox Code Playgroud)

过载解析的执行就像

__hypothetical __hyp{a.begin(), a.end()}; // braces
Run Code Online (Sandbox Code Playgroud)

因此,[over.match.list]进来了。

[...]重载解析分两个阶段选择构造函数:

  • 最初,候选函数是该类的初始化程序列表构造函数([dcl.init.list]),T参数列表由初始化程序列表作为单个参数组成。

  • [...]

构造器#2是一个初始化列表构造器。函数模板参数推导给出

Key = std::array<int>::iterator
Run Code Online (Sandbox Code Playgroud)

所以推导的类型s2

std::set<std::array<int>::iterator>
Run Code Online (Sandbox Code Playgroud)

的声明s2等效于

std::set<std::array<int>::iterator> s2 {a.begin(), a.end()};
Run Code Online (Sandbox Code Playgroud)

因此,s2是一组由两个元素组成的迭代器:a.begin()a.end()。在您的情况下,std::array<int>::iterator可能是int*和,a.begin()并且a.end()恰好分别被序列化为0x7ffecf9d12e00x7ffecf9d12f0


对于s3,执行重载解析就像在

__hypothetical __hyp(a.begin(), a.end()); // parentheses
Run Code Online (Sandbox Code Playgroud)

这是直接初始化,并且在[pver.match.ctor]的范围内。该initializer_list构造是无关紧要的,并且构造#1被选择来代替。函数模板参数推导给出

InputIterator = std::array<int>::iterator
Run Code Online (Sandbox Code Playgroud)

所以推导的类型s3

set<iterator_traits<std::array<int>::iterator>::value_type>
Run Code Online (Sandbox Code Playgroud)

哪个是set<int>。因此,的声明s3等于

std::set<int> s3 (a.begin(), a.end());
Run Code Online (Sandbox Code Playgroud)

s3int从范围[a.begin(), a.end())(四个元素)初始化的一组1, 2, 3, 4,它解释了输出。