为什么我不能在类范围内声明一个概念?

35

考虑以下代码:

struct A
{
    template <typename T>
    concept foo = true;
};

无法编译。我的Clang 10给出了错误:概念声明只能出现在全局或命名空间作用域中,GCC也说了类似的话。

不允许这样做的原因是什么?即使封闭类是模板,我也看不出为什么它不能工作。


2
看起来你需要一些文件,它将描述标准为什么在概念上强制全局/命名空间范围。因此召唤语言层。 - Marek R
4
@MarekR,我不确定是否适合使用“语言专家”标签。即使我可以引用标准指出不允许此代码的规则,但问题是在询问这些规则背后的原理。(关注此标签的用户更有可能能够回答,但我不清楚标签应该如何使用。) - cigien
2
@Christophe 嗯,这很有趣,当我提出/回答语言律师问题时,我只期望标准文本中规则的陈述。关于规则的意见,包括某些规则背后的动机,我认为是可选的。(这就是这个问题的全部要点)。 - cigien
1
@Christophe 我同意第二部分,但问题是,一个问题是否应该被标记为“语言律师”,仅仅因为它可能只能由语言律师回答?也许... - cigien
3
“语言律师的工作就是捍卫语言规则,必要时解释规则。”不对;他们会根据标准定义解释语言规则。解释规则形成的原因并不是语言律师的职责。这样的人可能会考虑为什么特定规则形成,或在标准的背景下为特定规则提供叙述意义,但这不是此标签的重点。此标签的重点在于表示答案需要引用标准,推理不行。 - Nicol Bolas
显示剩余4条评论
2个回答

37

可能出现的根本困难在于概念可能会变得依赖

template<class T>
struct A {
  template<T::Q X>
  void f();
};

X是一个(依赖)类型为T::Q的非类型模板参数(在C++20中不需要typename),还是一个受概念T::Q约束的类型模板参数?

规则是前者;我们需要新的语法(类似于typename/template)来表达其他可能性:也许像这样:

template<T::concept Q X> requires T::concept R<X*>
void A::g() {}

没有人认真探索过这样的扩展,它很容易与其他更有价值的概念语法扩展产生冲突。

4

为什么需要这样做,在@DavisHerring的回答中有所讨论。在这个回答中,我想分享一个当我遇到这个问题时实际上正在寻找的模式。

根据您的用例,您可能不需要概念定义。如果您只是想避免SFINAE技巧,您可以直接调用requires语句,并摆脱类范围内的任何概念:

struct A
{
    template<typename T>
    auto operator()(T t) const
    {
        if constexpr(requires { t.foo(); })
        {
            std::cout<<"foo"<<std::endl;
        }
        else
        {
            std::cout<<"no foo"<<std::endl;
        }
    }
};

并将其用作

struct B { auto foo() {} };
struct C {};

int main()
{
    A a;
    a(B());  //prints "foo"
    a(C());  //prints "no foo"
}

演示


然而,如果你发现在类中多次使用相同的requires语句,那么为什么不能在类范围内声明一次呢,这个问题是合理的。

因此,为了避免代码重复,你可以声明一个包含你要查找的概念的单个静态函数,例如:

struct A
{
    template<typename T>
    static constexpr auto concept_foo(T t)
    {
        return requires{ t.foo(); };
    }

    template<typename T>
    auto operator()(T t) const
    {        
        if constexpr(concept_foo(t))
        {
            std::cout<<"foo"<<std::endl;
        }
        else
        {
            std::cout<<"no foo"<<std::endl;
        }
    }
};

这种模式可以替代大多数类范围概念的使用情况。


这里本地lambda真正给你什么?为什么不直接写if constexpr(requires { t.foo(); })呢? - 303
@303:我也不知道你的版本是否有效。如果有效,那确实更好。 - davidhigh
1
@cppBeginner:是的,我在通用代码中使用过它,例如处理一般向量类型(有些提供operator[],其他提供operator(),当时我通过SFINAE分离了这两种情况)。更新后的版本也应该可以在没有模板技巧的情况下工作。 - davidhigh
太好了!我喜欢使用静态函数来替代类作用域中的概念,根据情况,模板化的静态变量也可能适用。唯一需要改变的是概念(或在这种情况下的函数)的名称,此答案提供了一些很好的见解。 - 303
@303:是的,我也同意。实际上,这里的概念仅仅是改变编译器发出的错误信息的类型(从“没有成员.foo()”到“约束条件foo未满足”)——这对于开销来说并不有用,也不是概念的本意。然而,我会保留它的原样,因为它传达了这个想法,并且在某种程度上仍然参考了OP。 - davidhigh
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接