I read about deduction guides for std::vector
from using cppreference.
Example:
#include <vector> int main() { std::vector<int> v = {1, 2, 3, 4}; std::vector x{v.begin(), v.end()}; // uses explicit deduction guide }
So, I have some questions about it:
What are std::vector
deduction guides in C++17?
Why and when do we need vector deduction?
Here, Is x
a std::vector<int>
or a std::vector<std::vector<int>>
?
1) std::vector is a sequence container that encapsulates dynamic size arrays. 2) std::pmr::vector is an alias template that uses a polymorphic allocator. The elements are stored contiguously, which means that elements can be accessed not only through iterators, but also using offsets to regular pointers to elements.
Class Template Argument Deduction (CTAD) is a C++17 Core Language feature that reduces code verbosity. C++17's Standard Library also supports CTAD, so after upgrading your toolset, you can take advantage of this new feature when using STL types like std::pair and std::vector.
What are
std::vector
deduction guides in C++17?
An user-defined deduction guide allows users to decide how class template argument deduction deduces arguments for a template class from its constructor arguments. In this case, it seems that std::vector
has an explicit guide that should make construction from an iterator pair more intuitive.
Why and when do we need vector deduction?
We don't "need" it, but it is useful in generic code and in code that's very obvious (i.e. code where explicitly specifying the template arguments is not beneficial to the reader).
Is
x
avector<int>
or avector<vector<int>>
?
Here's a nice trick to figure this out quickly - write a template function declaration without a definition and attempt to call it. The compiler will print out the type of the passed arguments. Here's what g++ 8 prints out:
template <typename> void foo(); // ... foo(x);
error: no matching function for call to
foo(std::vector<__gnu_cxx::__normal_iterator<int*, std::vector<int> > ...
As you can see from the error message, x
is deduced to std::vector<std::vector<int>::iterator>
.
Why?
std::vector
's deduction guides are available on cppreference.org. The Standard seems to define an explicit deduction guide from an iterator pair:
The behavior encountered in g++ 8 seems to be correct regardless, as (quoting 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>
is therefore the correct result when using list-initialization. live example
When constructing x
with std::vector x(v.begin(), v.end())
, int
will be deduced instead. live example
Here, Is
x
astd::vector<int>
or astd::vector<std::vector<int>>
?
The other answers here address your other questions, but I wanted to address this one a little more thoroughly. When we're doing class template argument deduction, we synthesize a bunch of function templates from the constructors, and then some more from deduction guides and perform overload resolution to determine the correct template parameters.
There are quite a few constructors to std::vector<T,A>
, but most of them don't mention T
which would make T
a non-deduced context and thus not a viable option in this overload. If we pre-prune the set to only use the ones that could be viable:
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
And then also this deduction guide, which I'll also simplify by dropping the allocator:
template <class InputIt> vector<typename std::iterator_traits<InputIt>::value_type> __f(InputIt, InputIt );
Those are our 5 candidates, and we're overloading as if by [dcl.init], a call via __f({v.begin(), v.end()})
. Because this is list-initialization, we start with the initializer_list
candidates and, only if there aren't any, do we proceed to the other candidates. In this case, there is an initializer_list
candidate that is viable (#8), so we select it without even considering any of the others. That candidate deduces T
as std::vector<int>::iterator
, so we then restart the process of overload resolution to select a constructor for std::vector<std::vector<int>::iterator>
list-initialized with two iterators.
This is probably not the desired outcome - we probably wanted a vector<int>
. The solution there is simple: use ()
s:
std::vector x(v.begin(), v.end()); // uses explicit deduction guide
Now, we're not doing list-initialization so the initializer_list
candidate isn't a viable candidate. As a result, we deduce vector<int>
through the deduction guide (the only viable candidate), and end up calling the iterator-pair constructor of it. This has the side benefit of actually making the comment correct.
This is one of the many places where initializing with {}
does something wildly different than initializing with ()
. Some argue that {}
is uniform initialization - which examples like this seem to counter. My rule of thumb: use {}
when you specifically, consciously need the behavior that {}
provides. ()
otherwise.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With