Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Inconsistent behavior with `empty` std ranges view depending on type

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.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading