作为 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) 方法中的概念究竟是如何检查的,以使其表现得像解释的那样?

(第一个问题对我来说比较重要)


我考虑过的一些可能的解决方案:

class B {
public:
    void print() const {/* ... */}
}

static_assert(LikeA<const B>);

(对于已经适应的 LikeA,当然),这也只是感觉不对。

static_assert(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。为什么?因为它在抽象上“更正确”。具体来说,有一堆关于模板参数类型推导如何工作的规则。