我阅读了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
扣除指南是什么?
为什么以及何时需要向量扣除?
此处,x
是std::vector<int>
还是std::vector<std::vector<int>>
?
答案 0 :(得分:18)
C ++ 17中的
std::vector
演绎指南是什么?
用户定义的演绎指南允许用户决定class template argument deduction如何从构造函数参数中推导出模板类的参数。在这种情况下,似乎std::vector
有一个明确的指南,应该使迭代器对的构造更直观。
为什么以及何时需要向量扣除?
我们没有&#34;需要&#34;它,但它在通用代码和代码非常明显(即明确指定模板参数的代码对读者无益的代码)中非常有用。
x
是vector<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。标准似乎从迭代器对定义了一个明确的演绎指南:
g ++ 8中遇到的行为似乎是正确的,因为(引用Rakete1111)
因此,使用列表初始化时,
- 只有在list-initialization 中尝试了所有
重载解析首选带有
std::initializer_list
的构造函数和支持的初始化列表std::initializer_list
构造函数后,才会考虑其他构造函数
std:vector<std::vector<int>::iterator>
是正确的结果。 live example
使用x
构建std::vector x(v.begin(), v.end())
时,会推断出int
。 live example
答案 1 :(得分:7)
此处,
x
是std::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()?
这是一个默认参数,它允许传入一个分配器。默认意味着您不需要两个指南。