33

Consider this code:

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

It doesn't compile. My Clang 10 gives me error: concept declarations may only appear in global or namespace scope, and GCC says something similar.

Is there a reason why it's not allowed? I don't see why it couldn't work, even if the enclosing class was a template.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • 2
    Looks like you need some paper which will describe rationale why standard enforces global/namespace scope on concepts. So summoning language layers. – Marek R Oct 07 '20 at 21:06
  • 4
    @MarekR I'm not sure the language-lawyer tag is appropriate. Even if I could point out the rules that disallow this code by citing the standard, the question is asking for the *rationale* behind these rules. (Users watching this tag are more likely to be able to answer, but I'm not clear on whether that's how a tag should be used.) – cigien Oct 07 '20 at 21:08
  • @cigien that's what language lawyers do: they defend the rules of the language, and if necessary, they explain why the rules are the rules. ;-) – Christophe Oct 07 '20 at 21:10
  • 2
    @Christophe Hmm, that's interesting, when I ask/answer language-lawyer questions, I expect only the rules as stated in the standard text. One's opinions about the rules, including the motivation behind some rules, is optional I think. (and that's the entire gist of this question). – cigien Oct 07 '20 at 21:16
  • @cigien Some lawyers deal with the rationale behind things. The rationale might be that this additional scoping option was not perceived as required, but it is interesting whether it was allowed in concepts TS and removed for some reason, or was never allowed. – Amir Kirsh Oct 07 '20 at 21:16
  • @cigien That's how I understand this tag too, so I've removed it. – HolyBlackCat Oct 07 '20 at 21:17
  • @cigien The first thing a language lawyer will do here is to check if clang is right. In doing so, they’ll research pretty much about the rules and when there is a clear reason, they may give it. Ir can be very objective, especially if the rule was extensively discussed in std committee. However, you are right. Some lawyers will stop at the rule. Conversely, I do not see how someone could answer and not know the underlying rules... ;-) – Christophe Oct 07 '20 at 21:21
  • 1
    @Christophe I agree with the second part, but then I guess the question is, should a question be tagged with language-lawyer, *solely* because it can probably only be answered by a language lawyer? Maybe... – cigien Oct 07 '20 at 21:26
  • 3
    @Christophe: "*that's what language lawyers do: they defend the rules of the language, and if necessary, they explain why the rules are the rules*". No; they *interpret* the rules of the language as defined by the standard. Explaining why they are the way they are is not part of what a language lawyer does. Such a person may be able to consider why a particular rule is the way it is or provide a narrative meaning for a particular rule within the context of the standard. But that's not the point of the tag. The point is to denote that answers need to site the standard. Reasoning cannot. – Nicol Bolas Oct 07 '20 at 22:25

2 Answers2

35

The fundamental difficulty that would arise is that concepts could become dependent:

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

Is X a non-type template parameter of (dependent) type T::Q (which does not require typename in C++20), or is it a type template parameter constrained by the concept T::Q?

The rule is that it’s the former; we would need new syntax (along the lines of typename/template) to express the other possibility: perhaps something like

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

No one has explored such an extension seriously, and it could easily conflict with other extensions to concept syntax that might be more valuable.

Davis Herring
  • 36,443
  • 4
  • 48
  • 76
3

The why is discussed in the answer by @DavisHerring. In this answer I want to share a pattern I was actually looking for when I encountered this question.

Depending on your use case, you maybe don't need the concept definition. If all you want to do is to avoid SFINAE trickery, you can directly invoke the requires clause and get rid of any concept at class scope:

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;
        }
    }
};

and use that as

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

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

DEMO


However, if you find yourself using the same requires statement multiple times in your class, the original question why you just can't declare it once at class scope is justified.

Thus, in order to work around code duplication, you can declare a single static function which contains the concept you are looking for, e.g.

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;
        }
    }
};

This pattern could replace most use cases for concepts at class scope.

davidhigh
  • 14,652
  • 2
  • 44
  • 75
  • What does the local lambda really gives you here, though? Why not simply write `if constexpr(requires { t.foo(); })`? – 303 Dec 08 '22 at 12:10
  • @303: didn't know your version worked either. If it does, it's indeed better. – davidhigh Dec 08 '22 at 19:35
  • Nice. Do you ever use this black magic in real code? – cppBeginner Dec 15 '22 at 02:10
  • For other readers, in cases that `T t` can't be invoked, please call lambda with template but without parameter e.g. `if constexpr(has_foo_.template operator()())`. See https://stackoverflow.com/questions/3575901 for more detail. – cppBeginner Dec 15 '22 at 03:21
  • @303: I changed the code according to your suggestion ... it's your answer now :-) – davidhigh Dec 15 '22 at 07:13
  • 1
    @cppBeginner: yes, I've used it in generic code, e.g. to treat general vector types (some provide `operator[]`, others `operator()`, and I separated the two cases back then via SFINAE). The updated version should also work without the `template` trickery. – davidhigh Dec 15 '22 at 07:17
  • Great! I like the idea of a static function as a replacement for a concept at class scope, depending on the situation, a templated static variable might also work. The only thing I would change is the name of the concept (or function in this case), [this](https://codereview.stackexchange.com/a/253381/267993) answer provides some great insights in that regard. – 303 Dec 16 '22 at 00:33
  • @303: yes, I agree with that too. The concept here is in fact merely changing the kind of error message the compiler emits (from "no member .foo()" to "constraint foo not satisfied") -- which is not useful for the overhead, and not what concepts are meant to be. Yet, I'll leave it the way it is because it gives the idea and still refers, in a way, to the OP. – davidhigh Dec 16 '22 at 06:10