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

时间:2019-05-26 11:37:58

标签: c++ c++17 uniform-initialization

如果我尝试为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  

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

1 个答案:

答案 0 :(得分:7)

简短回答

对于s2,使用大括号语法,并且{a.begin(), a.end()}被认为是initializer_list中的std::array<int>::iterator。因此,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

  

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

     
      
  • [...]

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

         
        
    • 模板参数(如果有)和功能参数是 deduction-guide 的参数。

    •   
    • 返回类型是推导指南 simple-template-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>;

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

  

初始化和过载解析按照   [dcl.init]和[over.match.ctor],[over.match.copy]或   [over.match.list](适用于初始化类型   对于假设类类型的对象,其中   所选功能和功能模板被认为是   该类类型的构造函数,以形成重载   设置,并且初始化器由哪个类的上下文提供   执行模板参数推导。每个这样的概念   如果该函数或函数认为构造函数是显式的   模板是根据构造函数或 deduction-guide   被声明为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>;

  // ...
};

对于s2的声明,

std::set s2 {a.begin(), a.end()};

重载解析的执行就像在

中一样
__hypothetical __hyp{a.begin(), a.end()}; // braces

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

  

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

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

  •   
  • [...]

  •   

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

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()分别被序列化为0x7ffecf9d12e00x7ffecf9d12f0


对于s3,就像在

中那样执行重载解析
__hypothetical __hyp(a.begin(), a.end()); // parentheses

这是直接初始化,并且在[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)开始初始化的,它解释了输出。