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

What are std::vector deduction guides in C++17?

我阅读了有关使用 cppreferencestd::vector 的推导指南。

示例:

#include <vector>

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

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

What are std::vector deduction guides in C++17?

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


Why and when do we need vector deduction?

我们不 "need" 它,但它在通用代码和非常明显的代码中很有用 (即显式指定模板参数的代码不利于 reader)


Is x a vector<int> or a vector<vector<int>>?

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

template <typename> 
void foo();

// ...

foo(x);

error: no matching function for call to foo(std::vector<__gnu_cxx::__normal_iterator<int*, std::vector<int> > ...

从报错信息可以看出,x推导为std::vector<std::vector<int>::iterator>


Why?

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

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

  • overload resolution prefers the constructor with std::initializer_list with the braced initializer list

  • other constructors are considered only after all std::initializer_list constructors have been tried in list-initialization

因此,

std:vector<std::vector<int>::iterator> 是使用列表初始化时的正确结果。 live example

std::vector x(v.begin(), v.end())构造x时,会推导intlive example

What are std::vector deduction guides in C++17?

Class template argument deduction 指定:"In order to instantiate a class template, every template argument must be known, but not every template argument has to be specified."

这是针对 std:vector 的本地化,我的意思是 std:vector 只是 class。没什么特别的。

这里是来自参考文献的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>;

如果您不熟悉语法,请阅读

Why and when do we need vector deduction?

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

Is x a vector<int> or a 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:

Why do we need that initialization, Alloc = Alloc() in that guide?

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

Here, Is x a std::vector<int> or a std::vector<std::vector<int>>?

此处的其他答案解决了您的其他问题,但我想更彻底地解决这个问题。当我们进行 class 模板参数推导时,我们 synthesize a bunch of function templates from the constructors, and then some more from 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),所以我们 select 它甚至没有考虑任何其他候选。该候选人将 T 推导为 std::vector<int>::iterator,因此我们重新启动重载决策过程,以 select 为 std::vector<std::vector<int>::iterator> 的构造函数使用两个迭代器进行列表初始化。

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

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

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


这是使用 {} 初始化与使用 () 初始化完全不同的许多地方之一。有些人认为 {} 是统一初始化——像这样的例子似乎与此相反。我的经验法则:当您特别有意识地需要 {} 提供的行为时,请使用 {}() 否则。