Home > Enterprise >  Inconsistent behavior with `empty` std ranges view depending on type
Inconsistent behavior with `empty` std ranges view depending on type

Time:11-16

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?

CodePudding user response:

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.

  • Related