2

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?

1
  • 1
    iterators can be pointers, and your B type is constructible from pointers so it makes sense that this can happen. Commented Nov 15, 2021 at 16:21

1 Answer 1

3

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Aha, so this is just another case of this particularly evil vector constructor overload hiding in unexpected places... In my case it was a vector of CComVariant, a legacy class from Windows SDK that has implicit constructors taking pointers and an attempt to use an empty view.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.