std :: set推演指南无法按预期工作

时间:2019-05-26 10:32:02

标签: c++17

两个集合类型不应该相同吗?

#include <array>
#include <set>
#include <iostream>

template <class Iter>
std::set(Iter, Iter) -> std::set<typename std::iterator_traits<Iter>::value_type>;

int main() {

    std::array a {1,2,3,4};
    std::set<int> si {a.begin(), a.end()}; 
    std::set       s {a.begin(), a.end()};     

    for(auto& i: si) { std::cout << i << "\n"; }
    for(auto& i: s ) { std::cout << i << "\n"; }

}

相反它产生:

1
2
3
4
0x7ffdf5bc9050
0x7ffdf5bc9060

甚至尝试使用其他分配器:-(

2 个答案:

答案 0 :(得分:1)

这里有两点困惑。


首先,推论指南必须在他们所指导的类模板的范围内,因此永远不会考虑该指南:

template <class Iter>
std::set(Iter, Iter) -> std::set<typename std::iterator_traits<Iter>::value_type>;

它必须看起来像这样:

namespace std {
    template <class Iter>
    set(Iter, Iter) -> set<typename iterator_traits<Iter>::value_type>;
}

但是您不允许将内容添加到namespace std中,因此请勿这样做。此外,此set的推导指南already exists

template<class InputIt, 
         class Comp = std::less<typename std::iterator_traits<InputIt>::value_type>,
         class Alloc = std::allocator<typename std::iterator_traits<InputIt>::value_type>>
set(InputIt, InputIt, Comp = Comp(), Alloc = Alloc())
  -> set<typename std::iterator_traits<InputIt>::value_type, Comp, Alloc>;

因此,没有任何理由要添加您的版本。


不使用推导指南的原因 ,或者如果将其放在正确的命名空间中也不会使用,则通常是列表初始化的主要警告:

如果有匹配的initializer_list<T>构造函数,则强烈推荐

特定的语言规则是,我们首先针对那些构造函数进行重载解析,然后再进行其余的工作。我们列表中的其他推导指南之一是:

template<class Key, class Comp = std::less<Key>, class Alloc = std::allocator<Key>>
set(std::initializer_list<Key>, Comp = Comp(), Alloc = Alloc())
  -> set<Key, Comp, Alloc>;

请注意:

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

的构造类型与完全相同:

std::set u{1, 2};

仅仅因为我们相同类型的两个元素是迭代器,并不意味着它们与相同类型的其他两个元素有不同的对待。这样我们得到了一组迭代器。

如果要将其他推导指南与其他构造函数一起使用,则必须 使用括号:

std::set s(a.begin(), a.end());     

并删除您的扣除指南!

答案 1 :(得分:0)

std::set提供了一个构造函数,该构造函数采用std::initializer_list,并且能够对T进行std::set<T>的类型推导。在这种情况下,使用语法std::set x{a.begin(), a.end() };将使用初始化程序列表构造函数并从中推导T。没有用户定义的扣除指南!

如果使用std::set x(a.begin(), a.end());创建对象,则将使用初始化列表构造器。现在,因为没有其他构造函数匹配,所以可以进行推导!

示例在代码中包含初始化器列表构造函数的情况下的效果:

template < typename U >
struct X
{
    // Directly makes the type deducable, because U can be deduced from the parameter of the constructor
    X( std::initializer_list<U> )
    {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }

    template < typename ... T>
        X( T... )
    {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};



struct ONE {};

template < typename Iter >
X( const Iter&, const Iter& ) -> X<ONE>;

template < typename U >
struct Y
{
    // type U can NOT deduced from the parameter!
    template < typename T>
    Y( std::initializer_list<T> )
    {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }

    template < typename ... T>
        Y( T... )
    {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};



struct TWO {};

template < typename Iter >
Y( const Iter&, const Iter& ) -> Y<TWO>;

int main()
{
    std::array a {1,2,3,4};
    X x1{a.begin(), a.end()};
    X x2(a.begin(), a.end());

    std::cout << "###" << std::endl;

    Y y1{a.begin(), a.end()};
    Y y2(a.begin(), a.end());
}

我对初始化器列表构造函数的优先级高于推导指南,这对我来说也是新的,并在此处回答: Why does using uniform initializer syntax result in different behavior to the "old" style ()?

对我来说重要的新事物是:

[over.match.list]

  

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

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

  •   
  • [...]

  •   

有关详细信息,请参见链接的问题!