当我决定修改提供的代码示例以查看它是否可以与 std::list
一起使用时,我正在阅读 Rainer Grimm 的书
C++20:获取详细信息有关定义自己的视图类型的第 5.1.7.2 节。代码最终如下所示:
#include <concepts>
#include <iostream>
#include <list>
#include <ranges>
#include <vector>
template <std::ranges::input_range Range>
requires std::ranges::view<Range>
class ContainerView : public std::ranges::view_interface<ContainerView<Range>> {
private:
std::ranges::iterator_t<Range> begin_ {};
std::ranges::sentinel_t<Range> end_ {};
Range range_ {};
public:
constexpr ContainerView() = default;
constexpr ContainerView(Range r)
: begin_(std::begin(r))
, end_(std::end(r))
, range_(std::move(r))
{
}
constexpr auto begin() const { return begin_; }
constexpr auto end() const { return end_; }
};
int main()
{
std::list my_list { 1, 2, 3 };
auto my_container_view { ContainerView(std::views::all(my_list)) };
for (auto const& c : my_container_view)
std::cout << c << '\n';
}
该程序使用 x86-64 clang++ v17.0.1 和标志
-std=c++20 -fsanitize=undefined -fsanitize=address -Wall -Wextra -Werror
成功编译,并且在运行时,它会打印数字 1、2 和 3,并按预期正常退出。
但是,出于好奇,当我更改
main
函数以将 my_list
包装在对 std::move
的调用中并将其传递给 std::views::all
时,程序似乎陷入了无限递归,重复打印以下元素名单。
int main()
{
std::list my_list { 1, 2, 3 };
auto my_container_view { ContainerView(std::views::all(std::move(my_list))) };
for (auto const& c : my_container_view)
std::cout << c << '\n';
}
我发现奇怪的是,当我简单地将
std::list
替换为 std::vector
并保持其余代码不变时,程序会打印每个元素一次并优雅退出:
int main()
{
std::vector my_vec { 1, 2, 3 };
auto my_container_view { ContainerView(std::views::all(std::move(my_vec))) };
for (auto const& c : my_container_view)
std::cout << c << '\n';
}
当我删除
ContainerView
类型的间接并直接迭代 std::list
时,我也没有得到异常行为:
int main()
{
std::list my_list { 1, 2, 3 };
for (auto const& c : std::move(my_list))
std::cout << c << '\n';
}
鉴于代码的每个变体都会编译并不会产生编译时间约束错误,是否存在我将
std::move
和 std::list
与自定义视图类型结合使用而违反的语义约束,这会导致明显的未定义行为?
遵循@273K的建议,确保在将
begin_
end_
移动到成员初始值设定项列表中的Range
之后,在构造函数主体中初始化r
和range_
可以消除未定义的行为。
#include <concepts>
#include <iostream>
#include <list>
#include <ranges>
#include <vector>
template <std::ranges::input_range Range>
requires std::ranges::view<Range>
class ContainerView : public std::ranges::view_interface<ContainerView<Range>> {
private:
std::ranges::iterator_t<Range> begin_ {};
std::ranges::sentinel_t<Range> end_ {};
Range range_ {};
public:
constexpr ContainerView() = default;
constexpr ContainerView(Range r)
: range_(std::move(r))
{
begin_ = std::begin(range_);
end_ = std::end(range_);
}
constexpr auto begin() const { return begin_; }
constexpr auto end() const { return end_; }
};
int main()
{
std::list my_list { 1, 2, 3 };
auto my_container_view { ContainerView(std::views::all(std::move(my_list))) };
for (auto const& c : my_container_view)
std::cout << c << '\n';
}