Consider the following code snippet:
#include <vector>
#include <ranges>
#include <iostream>
struct A
{
};
struct B
{
B(void *) {};
};
template<class T, class R>
std::vector<T> foo(R &&range)
{
return std::vector<T> {
std::ranges::begin(range),
std::ranges::end(range)
};
}
int main()
{
std::cout << foo<A>(std::views::empty<A>).size() << "\n";
std::cout << foo<B>(std::views::empty<B>).size() << "\n";
}
It correctly prints 0 on the first line and incorrectly prints 2 on the second line on GCC 11.2 (https://godbolt.org/z/s6aoTGbr4) and MSVC 19.30.30705 (Visual Studio 2022 17.0).
Apparently in the second case std::views::empty view produces iterators that causes constructor taking initializer list to be selected.
Changing std::vector<T> { ... } to std::vector<T> ( ... ) fixes this, but I wonder if it is actually a bug in implementation (or even definition) of the empty view?
>Solution :
This is why you should be wary of list-initialization! In particular, this syntax:
std::vector<T>{ ... }
should only (!!) be used when you’re specifically trying to invoke the std::initializer_list<T> constructor, and specifically providing elements to the vector and not in any other case. Like this one, where you’re not trying to call that constructor.
The issue is that in list-initialization, the std::initializer_list constructor is strongly preferred. If it is at all viable, it is chosen first. In this case, we are trying to construct a vector<B> from two pointers to B (that’s the iterator type for empty<B>). A B is constructible from a B* (via the void* constructor), which makes the std::initializer_list<B> constructor a viable candidate, which means it’s selected.
As a result, you get a vector<B> with two Bs, each constructed form a null pointer.
For A, there’s no such viable constructor, so you fallback to normal initialization, which will select the iterator pair constructor, giving you an empty vector<A>.
The fix here is to use ()s when you initialize, since that’s actually what you intend to do.