#include <vector>
int main()
{
auto v = std::vector{std::vector<int>{}};
return v.front().empty(); // error
}
See online demo
However, according to Scott Meyers' Effective Modern C (emphasis in original):
If, however, one or more constructors declare a parameter of type
std::initializer_list
, calls using the braced initialization syntax strongly prefer the overloads takingstd::initializer_lists
. Strongly. If there's any way for compilers to construe a call using a braced initializer to be a constructor taking astd::initializer_list
, compilers will employ that interpretation.
So, I think std::vector{std::vector<int>{}};
should produce an object of std::vector<std::vector<int>>
rather than std::vector<int>
.
Who is wrong? and why?
CodePudding user response:
Meyers is mostly correct (the exception is that T{}
is value-initialization if a default constructor exists), but his statement is about overload resolution. That takes place after CTAD, which chooses the class (and hence the set of constructors) to use.
CTAD doesn’t “prefer” initializer-list constructors in that it prefers copying to wrapping for nestable templates like std::vector
or std::optional
. (It’s possible to override this with deduction guides, but the standard library uses the default, as one might expect.) This makes some sense in that it prevents creating strange types like std::optional<std::optional<int>>
, but it makes generic code harder to write because it gives
template<class T> void f(T x) {
std::vector v{x};
// …
}
a meaning that depends on the type of its argument in an irregular and non-injective fashion. In particular, v
might be std::vector<int>
with T
=int
or with T
=std::vector<int>
, despite being std::vector<std::deque<int>>
if T
=std::deque<int>
. It’s unfortunate that a tool for computing one type based on some others is not usable in a generic context.
CodePudding user response:
auto v = std::vector{std::vector<int>{}};
actually creates a std::vector<int>
because it uses std::vector
copy constructor. It is interpreted by the compiler as:
auto vTemp = std::vector<int>{};
auto v = std::vector<int>( vTemp );
So v
ends up being a std::vector<int>
, not a std::vector<std::vector<int>>
.
As reported by "P Kramer" and "Marek P" in comments, the following syntaxes will help any compiler accomplishing what you expect:
auto v = std::vector{{std::vector<int>{}}};
auto v = std::vector<std::vector<int>>{ std::vector<int>{} };