范围适配器的 begin/end 的 const 重载是否受限?
Is the const overload of begin/end of the range adapters underconstrained?
在 C++20 中,一些范围同时具有 const
和非 const
begin()/end()
,而其他范围仅具有非 const
begin()/end()
.
为了让包裹前者的范围适配器能够在const
合格时使用begin()/end()
,一些范围适配器如elements_view
, reverse_view
and common_view
都提供了约束const
-限定的begin()/end()
函数,例如:
template<view V>
requires (!common_range<V> && copyable<iterator_t<V>>)
class common_view : public view_interface<common_view<V>> {
public:
constexpr auto begin();
constexpr auto end();
constexpr auto begin() const requires range<const V>;
constexpr auto end() const requires range<const V>;
};
template<input_range V, size_t N>
requires view<V> && has-tuple-element<range_value_t<V>, N>
class elements_view : public view_interface<elements_view<V, N>> {
public:
constexpr auto begin();
constexpr auto end();
constexpr auto begin() const requires range<const V>;
constexpr auto end() const requires range<const V>;
};
begin() const/end() const
只有在const V
满足range
时才会被实例化,这似乎是合理的。
但是仅仅一个简单的约束似乎是不够的。以common_view
为例,要求V
不是common_range
。 但是当V
不是common_range
并且const V
是common_range
时(我知道这样的范围非常奇怪,但是理论上是可以存在的,godbolt):
#include <ranges>
struct weird_range : std::ranges::view_base {
int* begin();
const int* end();
std::common_iterator<int*, const int*> begin() const;
std::common_iterator<int*, const int*> end() const;
};
int main() {
weird_range r;
auto cr = r | std::views::common;
static_assert(std::ranges::forward_range<decltype(cr)>); // ok
cr.front(); // ill-formed
}
在上面的例子中,const V
仍然满足range
的概念,当我们将r
应用到views::common
时,它的front()
功能将是格式错误。
原因是view_interface::front() const
仍然会被实例化,而common_iterator
会在common_view
的begin() const
内部构造,这样会导致hard error中止编译,因为 const V
本身是 common_range
.
同样,我们也可以根据相同的概念创建一个奇怪的范围,使views::reverse
和views::keys
的front()
失败(godbolt):
#include <ranges>
struct my_range : std::ranges::view_base {
std::pair<int, int>* begin();
std::pair<int, int>* end();
std::common_iterator<int*, const int*> begin() const;
std::common_iterator<int*, const int*> end() const;
};
int main() {
my_range r;
auto r1 = r | std::views::reverse;
static_assert(std::ranges::random_access_range<decltype(r1)>); // ok
r1.front(); // ill-formed
auto r2 = r | std::views::keys;
static_assert(std::ranges::random_access_range<decltype(r2)>); // ok
r2.front(); // ill-formed
}
那么,范围适配器 begin()/end()
的 const
重载是欠约束的,还是 weird_range
本身的定义不正确?这可以被视为标准缺陷吗?
本题主要受到LWG 3592, which states that for lazy_split_view
, we need to consider the case where the const Pattern
is not range
, and then I subsequently submitted the LWG 3599的启发。当我进一步查看其他范围适配器的 begin() const
时,我发现它们中的大多数只要求 const V
为 range
,这个看似松散的约束让我提出了这个问题。
为了启用范围适配器的 begin() const
,理论上,const V
的约束应该与 V
完全相同,这意味着 long list of constraints在 V
上,例如 elements_view
,需要替换为 const V
而不是仅约束 const V
成为 range
.
但实际上,似乎标准对V
和const V
的iterator和sentinel类型相差很大的情况不感兴趣
最近 SG9 关于 LWG3564 的讨论得出的结论是,预期的设计是 x
和 as_const(x)
应该被要求在保持相等的表达式中以相同的结果替换,而两者都是有效的。换句话说,它们在[concepts.equality]“相同的柏拉图价值观”意义上应该是“平等的”。因此,例如,x
和 as_const(x)
具有完全不同的元素是无效的。
规则的确切措辞和范围必须等待论文,我们必须注意避免禁止合理的代码。但可以肯定的是,像“x
是一对的范围,但 as_const(x)
是一个整数的范围”之类的东西不在“合理”的任何合理定义范围内。
在 C++20 中,一些范围同时具有 const
和非 const
begin()/end()
,而其他范围仅具有非 const
begin()/end()
.
为了让包裹前者的范围适配器能够在const
合格时使用begin()/end()
,一些范围适配器如elements_view
, reverse_view
and common_view
都提供了约束const
-限定的begin()/end()
函数,例如:
template<view V>
requires (!common_range<V> && copyable<iterator_t<V>>)
class common_view : public view_interface<common_view<V>> {
public:
constexpr auto begin();
constexpr auto end();
constexpr auto begin() const requires range<const V>;
constexpr auto end() const requires range<const V>;
};
template<input_range V, size_t N>
requires view<V> && has-tuple-element<range_value_t<V>, N>
class elements_view : public view_interface<elements_view<V, N>> {
public:
constexpr auto begin();
constexpr auto end();
constexpr auto begin() const requires range<const V>;
constexpr auto end() const requires range<const V>;
};
begin() const/end() const
只有在const V
满足range
时才会被实例化,这似乎是合理的。
但是仅仅一个简单的约束似乎是不够的。以common_view
为例,要求V
不是common_range
。 但是当V
不是common_range
并且const V
是common_range
时(我知道这样的范围非常奇怪,但是理论上是可以存在的,godbolt):
#include <ranges>
struct weird_range : std::ranges::view_base {
int* begin();
const int* end();
std::common_iterator<int*, const int*> begin() const;
std::common_iterator<int*, const int*> end() const;
};
int main() {
weird_range r;
auto cr = r | std::views::common;
static_assert(std::ranges::forward_range<decltype(cr)>); // ok
cr.front(); // ill-formed
}
在上面的例子中,const V
仍然满足range
的概念,当我们将r
应用到views::common
时,它的front()
功能将是格式错误。
原因是view_interface::front() const
仍然会被实例化,而common_iterator
会在common_view
的begin() const
内部构造,这样会导致hard error中止编译,因为 const V
本身是 common_range
.
同样,我们也可以根据相同的概念创建一个奇怪的范围,使views::reverse
和views::keys
的front()
失败(godbolt):
#include <ranges>
struct my_range : std::ranges::view_base {
std::pair<int, int>* begin();
std::pair<int, int>* end();
std::common_iterator<int*, const int*> begin() const;
std::common_iterator<int*, const int*> end() const;
};
int main() {
my_range r;
auto r1 = r | std::views::reverse;
static_assert(std::ranges::random_access_range<decltype(r1)>); // ok
r1.front(); // ill-formed
auto r2 = r | std::views::keys;
static_assert(std::ranges::random_access_range<decltype(r2)>); // ok
r2.front(); // ill-formed
}
那么,范围适配器 begin()/end()
的 const
重载是欠约束的,还是 weird_range
本身的定义不正确?这可以被视为标准缺陷吗?
本题主要受到LWG 3592, which states that for lazy_split_view
, we need to consider the case where the const Pattern
is not range
, and then I subsequently submitted the LWG 3599的启发。当我进一步查看其他范围适配器的 begin() const
时,我发现它们中的大多数只要求 const V
为 range
,这个看似松散的约束让我提出了这个问题。
为了启用范围适配器的 begin() const
,理论上,const V
的约束应该与 V
完全相同,这意味着 long list of constraints在 V
上,例如 elements_view
,需要替换为 const V
而不是仅约束 const V
成为 range
.
但实际上,似乎标准对V
和const V
的iterator和sentinel类型相差很大的情况不感兴趣
最近 SG9 关于 LWG3564 的讨论得出的结论是,预期的设计是 x
和 as_const(x)
应该被要求在保持相等的表达式中以相同的结果替换,而两者都是有效的。换句话说,它们在[concepts.equality]“相同的柏拉图价值观”意义上应该是“平等的”。因此,例如,x
和 as_const(x)
具有完全不同的元素是无效的。
规则的确切措辞和范围必须等待论文,我们必须注意避免禁止合理的代码。但可以肯定的是,像“x
是一对的范围,但 as_const(x)
是一个整数的范围”之类的东西不在“合理”的任何合理定义范围内。