什么是C ++ 17中的std :: vector deduction指南?

时间:2017-09-01 08:14:56

标签: c++ templates vector c++17 template-deduction

我阅读了std::vector使用cppreference的扣除指南。

示例:

#include <vector>

int main() {
   std::vector<int> v = {1, 2, 3, 4};
   std::vector x{v.begin(), v.end()}; // uses explicit deduction guide
}

所以,我对此有一些疑问:

  • C ++ 17中的std::vector扣除指南是什么?

  • 为什么以及何时需要向量扣除?

  • 此处,xstd::vector<int>还是std::vector<std::vector<int>>

3 个答案:

答案 0 :(得分:18)

  

C ++ 17中的std::vector演绎指南是什么?

用户定义的演绎指南允许用户决定class template argument deduction如何从构造函数参数中推导出模板类的参数。在这种情况下,似乎std::vector有一个明确的指南,应该使迭代器对的构造更直观。

  

为什么以及何时需要向量扣除?

我们没有&#34;需要&#34;它,但它在通用代码和代码非常明显(即明确指定模板参数的代码对读者无益的代码)中非常有用。

  

xvector<int>还是vector<vector<int>>

这是一个很好的技巧,可以快速解决这个问题 - 编写一个没有定义的模板函数声明并尝试调用它。编译器将打印出传递的参数的类型。这是g ++ 8打印出来的内容:

template <typename> 
void foo();

// ...

foo(x);
  

错误:没有匹配函数来调用foo(std::vector<__gnu_cxx::__normal_iterator<int*, std::vector<int> > ...

从错误消息中可以看出,x被推断为std::vector<std::vector<int>::iterator>

  

为什么呢?

std::vector的扣除指南are available on cppreference.org。标准似乎从迭代器对定义了一个明确的演绎指南:

enter image description here

g ++ 8中遇到的行为似乎是正确的,因为(引用Rakete1111

  
      
  • 重载解析首选带有std::initializer_list的构造函数和支持的初始化列表

  •   只有在list-initialization

    中尝试了所有std::initializer_list构造函数后,才会考虑
  • 其他构造函数   

因此,使用列表初始化时,

std:vector<std::vector<int>::iterator>是正确的结果。 live example

使用x构建std::vector x(v.begin(), v.end())时,会推断出intlive example

答案 1 :(得分:7)

  

此处,xstd::vector<int>还是std::vector<std::vector<int>>

这里的其他答案解决了你的其他问题,但我想更彻底地解决这个问题。当我们进行类模板参数推导时,我们从构造函数中synthesize a bunch of function templates,然后从deduction guides获得更多,并执行重载决策以确定正确的模板参数。

std::vector<T,A>有很多构造函数,但大多数都没有提到T,这会使T成为非推断的上下文,因此不是一个可行的选项。超载。如果我们预先修剪集合只使用那些可行的集合:

template <class T> vector<T> __f(size_t, T const& );    // #2
template <class T> vector<T> __f(vector<T> const& );    // #5
template <class T> vector<T> __f(vector<T>&& );         // #6, NB this is an rvalue ref
template <class T> vector<T> __f(initializer_list<T> ); // #8

然后还有这个deduction guide,我也将通过删除分配器来简化:

template <class InputIt>
vector<typename std::iterator_traits<InputIt>::value_type> __f(InputIt, InputIt );

这些是我们的5个候选人,我们正在通过[dcl.init]进行超载,通过__f({v.begin(), v.end()})拨打电话。因为这是列表初始化,我们start with the initializer_list candidates,并且,只有在没有任何时候,我们才会继续其他候选人。在这种情况下,有一个initializer_list候选者是可行的(#8),所以我们选择它而不考虑任何其他候选者。该候选者将T推导为std::vector<int>::iterator,因此我们重新启动重载解析过程,以选择std::vector<std::vector<int>::iterator>列表初始化的构造函数,并使用两个迭代器。

这可能不是理想的结果 - 我们可能想要一个vector<int>。解决方案很简单:使用() s:

std::vector x(v.begin(), v.end()); // uses explicit deduction guide

现在,我们没有进行列表初始化,因此initializer_list候选者不是一个可行的候选者。因此,我们通过演绎指南(唯一可行的候选者)推导出vector<int>,并最终调用它的迭代器对构造函数。这有利于实际使评论正确。

这是使用{}初始化与使用()初始化完全不同的地方之一。有人认为{}是统一初始化 - 这样的例子似乎是反驳的。我的经验法则:当你具体地使用{}时,有意识地需要{}提供的行为。否则()

答案 2 :(得分:5)

  

C ++ 17中的std::vector演绎指南是什么?

Class template argument deduction指定:“为了实例化一个类模板,必须知道每个模板参数,但不是必须指定每个模板参数。”

这是std:vector的本地化,我的意思是std:vector只是一个类。没什么特别的。

以下是参考资料中的std::vector教育指南:

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

如果您不熟悉语法,请阅读What are template deduction guides in C++17?

  

为什么以及何时需要向量扣除?

当从参数中推断出类型不是基于其中一个参数的类型时,您需要指南。

  

x是vector<int>还是vector<vector<int>>

既不!

这是:

std::vector<std::vector<int>::iterator>

强制发生简单的编译错误(例如,通过为x分配一个数字)将显示其类型):

error: no match for 'operator=' (operand types are 'std::vector<__gnu_cxx::__normal_iterator<int*, std::vector<int> >, std::allocator<__gnu_cxx::__normal_iterator<int*, std::vector<int> > > >' and 'int')

PS:

  

为什么我们需要在该指南中进行初始化,Alloc = Alloc()?

这是一个默认参数,它允许传入一个分配器。默认意味着您不需要两个指南。