C++17 中的 std::vector 演绎指南是什么?
What are std::vector deduction guides in C++17?
我阅读了有关使用 cppreference 的 std::vector
的推导指南。
示例:
#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>>
?
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
时,会推导int
。 live 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>
(唯一可行的候选者),并最终调用了它的迭代器对构造函数。这具有实际使评论正确的附带好处。
这是使用 {}
初始化与使用 ()
初始化完全不同的许多地方之一。有些人认为 {}
是统一初始化——像这样的例子似乎与此相反。我的经验法则:当您特别有意识地需要 {}
提供的行为时,请使用 {}
。 ()
否则。
我阅读了有关使用 cppreference 的 std::vector
的推导指南。
示例:
#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>>
?
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
avector<int>
or avector<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 listother 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
时,会推导int
。 live 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 avector<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
astd::vector<int>
or astd::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>
(唯一可行的候选者),并最终调用了它的迭代器对构造函数。这具有实际使评论正确的附带好处。
这是使用 {}
初始化与使用 ()
初始化完全不同的许多地方之一。有些人认为 {}
是统一初始化——像这样的例子似乎与此相反。我的经验法则:当您特别有意识地需要 {}
提供的行为时,请使用 {}
。 ()
否则。