根据类型,“空”标准范围视图的行为不一致

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

考虑以下代码片段:

#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";
}

在 GCC 11.2 (https://godbolt.org/z/s6aoTGbr4) 和 MSVC 19.30.30705 (Visual Studio 2022 17.0).

显然在第二种情况下 std::views::empty 视图生成迭代器,导致构造函数选择初始化列表。

std::vector<T> { ... } 更改为 std::vector<T> ( ... ) 可以解决此问题,但我想知道它是否真的是 empty 视图的实现(甚至定义)中的错误?

这就是您应该警惕列表初始化的原因!特别是这种语法:

std::vector<T>{ ... }
当您专门尝试调用 std::initializer_list<T> 构造函数并专门为 vector 提供元素时,

应该 (!!)而不是在任何其他情况下。就像这一个,您不会尝试调用该构造函数。


问题是在列表初始化中,强烈推荐 std::initializer_list 构造函数。如果它完全可行,则首先选择它。在这种情况下,我们试图从两个指向 B 的指针构造一个 vector<B>(这是 empty<B> 的迭代器类型)。 B 可以从 B* 构造(通过 void* 构造函数),这使得 std::initializer_list<B> 构造函数成为可行的候选者,这意味着它是 selected.

因此,您得到一个 vector<B> 和两个 B,每个都由一个空指针构成。

对于 A,没有这样可行的构造函数,因此您回退到正常初始化,这将 select 迭代器对构造函数,给您一个空的 vector<A>.

此处的解决方法是在初始化时使用 ()s,因为这实际上是您打算做的。