作为 return 键入 requires 子句的概念的常量版本
Constant version of a concept as return type in requires clause
我有一些 class A
有一些 const
和一些非 const
功能,以及一个合适的概念,比如
class A {
public:
void modify() {/* ... */}
void print() const {/* ... */}
}
template<typename T>
concept LikeA = requires (T t, const T const_t) {
{ t.modify() } -> std::same_as<void>;
{ const_t.print() } -> std::same_as<void>;
}
static_assert(LikeA<A>);
我注意到的一件好事是,对于某些采用 const LikeA auto &a
的函数,下面的代码实际上是合法的:
void use (const LikeA auto &a) {
a.print(); // fine, print is a const method
// a.modify(); // illegal, a is a const reference [at least for use(A a) itself, but this is not my point here]
}
static_assert(!LikeA<const A>);
int main() {
A a1;
use(a1); //clear, as LikeA<A> holds
const A a2;
use(a2); //not obvious, as LikeA<const A> does not hold
}
我查看了 cppreference
上的定义,我无法真正解释这种行为,我预计它是非法的,尽管直觉上,这确实是我想要的。
现在谈谈我的真实情况:我有一个持有人 class AHolder
,returns const A&
作为其方法之一,我想要一个合适的概念对于这个持有人 class 也适用于持有任何满足 LikeA
的任何其他持有人,所以我尝试了:
class AHolder {
public:
const A& getA() {return _a;}
private:
const A _a;
};
template<typename T>
concept LikeAHolder = requires (T t) {
{t.getA() } -> LikeA;
};
static_assert(LikeAHolder<AHolder>); //fails
这失败了,因为 const A&
根本不满足 LikeA
,所以我很乐意将其调整为
template<typename T>
concept LikeAHolder = requires (T t) {
{t.getA() } -> const LikeA; //invalid syntax
};
static_assert(LikeAHolder<AHolder>);
本着与 use
方法同样接受 const A
.
的示例精神
是否有这样的语法要求t.getA
的return类型满足LikeA
,同时考虑到return类型将是const
?
此外,use(const LikeA auto &a)
方法中的概念究竟是如何检查的,以使其表现得像解释的那样?
(第一个问题对我来说比较重要)
我考虑过的一些可能的解决方案:
- Return 非常量引用。这会使上面的代码非法,但当然会严重破坏
const
-正确性,因为 _a
也必须是非 const
并且用户可以更改 const
的私有属性=23=]。这对我来说没有选择。
- 有两个概念
LikeA
和LikeConstA
。 return 类型可以是 LikeConstA
只需要 const
方法。这应该行得通,但感觉真的很笨拙,而且真的不是应该如何使用概念,这也引入了最终用户所必需的更多概念,他们不得不打扰他们,等等。
- 在概念
LikeA
中,检查模板化类型T
是否为常量(通过std::is_const
),如果是,则不需要非const
-方法。这在上面的例子中有效,但有我们现在简单地拥有的不良影响
class B {
public:
void print() const {/* ... */}
}
static_assert(LikeA<const B>);
(对于已经适应的 LikeA
,当然),这也只是感觉不对。
- 在
LikeA
的定义中,使用std::remove_reference
和std::const_cast
抛弃引用/常量,然后检查所需的函数。首先,我不知道这是否总是适用于更复杂的类型,但即便如此,这现在也会产生不良影响
static_assert(LikeA<const A>);
将是真实的,打破(或至少扭曲)概念的语义。
为了总结并确保您不会误会我的意思,我想有一种方法
- 执行
const
-正确性
- 不使用第二个概念'for the end-user'。我的意思是,当然可以定义辅助概念或使用一些有助于定义上述概念的标准库,但实际上不需要使用
A
和 LikeA
等。
- 不是 简单地忽略 const 类型的非常量要求(正如我提到的,这在编译器方面是可以的,但在语义上感觉不对)
- 没有定义
LikeA<const A>
为真
理想情况下,只有一个功能像已经提到的那样工作
template<typename T>
concept LikeAHolder = requires (T t) {
{t.getA() } -> const LikeA; //invalid syntax
};
{[](const LikeA auto&){}( t.getA() )}
请注意,非const&
返回getA
将通过此。
我制作了一个进行概念检查的 lambda,然后确保 t.getA()
通过它。
void use (const LikeA auto &a)
这是shorthand
template<LikeA A>
void use (const A&a)
并且当使用 T const
调用时,它推断出 A=T
而不是 A=T const
。为什么?因为它在抽象上“更正确”。具体来说,有一堆关于模板参数类型推导如何工作的规则。
我有一些 class A
有一些 const
和一些非 const
功能,以及一个合适的概念,比如
class A {
public:
void modify() {/* ... */}
void print() const {/* ... */}
}
template<typename T>
concept LikeA = requires (T t, const T const_t) {
{ t.modify() } -> std::same_as<void>;
{ const_t.print() } -> std::same_as<void>;
}
static_assert(LikeA<A>);
我注意到的一件好事是,对于某些采用 const LikeA auto &a
的函数,下面的代码实际上是合法的:
void use (const LikeA auto &a) {
a.print(); // fine, print is a const method
// a.modify(); // illegal, a is a const reference [at least for use(A a) itself, but this is not my point here]
}
static_assert(!LikeA<const A>);
int main() {
A a1;
use(a1); //clear, as LikeA<A> holds
const A a2;
use(a2); //not obvious, as LikeA<const A> does not hold
}
我查看了 cppreference
上的定义,我无法真正解释这种行为,我预计它是非法的,尽管直觉上,这确实是我想要的。
现在谈谈我的真实情况:我有一个持有人 class AHolder
,returns const A&
作为其方法之一,我想要一个合适的概念对于这个持有人 class 也适用于持有任何满足 LikeA
的任何其他持有人,所以我尝试了:
class AHolder {
public:
const A& getA() {return _a;}
private:
const A _a;
};
template<typename T>
concept LikeAHolder = requires (T t) {
{t.getA() } -> LikeA;
};
static_assert(LikeAHolder<AHolder>); //fails
这失败了,因为 const A&
根本不满足 LikeA
,所以我很乐意将其调整为
template<typename T>
concept LikeAHolder = requires (T t) {
{t.getA() } -> const LikeA; //invalid syntax
};
static_assert(LikeAHolder<AHolder>);
本着与 use
方法同样接受 const A
.
是否有这样的语法要求t.getA
的return类型满足LikeA
,同时考虑到return类型将是const
?
此外,use(const LikeA auto &a)
方法中的概念究竟是如何检查的,以使其表现得像解释的那样?
(第一个问题对我来说比较重要)
我考虑过的一些可能的解决方案:
- Return 非常量引用。这会使上面的代码非法,但当然会严重破坏
const
-正确性,因为_a
也必须是非const
并且用户可以更改const
的私有属性=23=]。这对我来说没有选择。 - 有两个概念
LikeA
和LikeConstA
。 return 类型可以是LikeConstA
只需要const
方法。这应该行得通,但感觉真的很笨拙,而且真的不是应该如何使用概念,这也引入了最终用户所必需的更多概念,他们不得不打扰他们,等等。 - 在概念
LikeA
中,检查模板化类型T
是否为常量(通过std::is_const
),如果是,则不需要非const
-方法。这在上面的例子中有效,但有我们现在简单地拥有的不良影响
class B {
public:
void print() const {/* ... */}
}
static_assert(LikeA<const B>);
(对于已经适应的 LikeA
,当然),这也只是感觉不对。
- 在
LikeA
的定义中,使用std::remove_reference
和std::const_cast
抛弃引用/常量,然后检查所需的函数。首先,我不知道这是否总是适用于更复杂的类型,但即便如此,这现在也会产生不良影响
static_assert(LikeA<const A>);
将是真实的,打破(或至少扭曲)概念的语义。
为了总结并确保您不会误会我的意思,我想有一种方法
- 执行
const
-正确性 - 不使用第二个概念'for the end-user'。我的意思是,当然可以定义辅助概念或使用一些有助于定义上述概念的标准库,但实际上不需要使用
A
和LikeA
等。 - 不是 简单地忽略 const 类型的非常量要求(正如我提到的,这在编译器方面是可以的,但在语义上感觉不对)
- 没有定义
LikeA<const A>
为真
理想情况下,只有一个功能像已经提到的那样工作
template<typename T>
concept LikeAHolder = requires (T t) {
{t.getA() } -> const LikeA; //invalid syntax
};
{[](const LikeA auto&){}( t.getA() )}
请注意,非const&
返回getA
将通过此。
我制作了一个进行概念检查的 lambda,然后确保 t.getA()
通过它。
void use (const LikeA auto &a)
这是shorthand
template<LikeA A>
void use (const A&a)
并且当使用 T const
调用时,它推断出 A=T
而不是 A=T const
。为什么?因为它在抽象上“更正确”。具体来说,有一堆关于模板参数类型推导如何工作的规则。