3

Let's say I start with this simple example of the use of a C++20 "concept":

template <typename T>
concept HasFoo = requires( T t )
{
    t.foo();
};

template <HasFoo T>
void DoFoo( T& thing )
{
    thing.foo();
}

class FooThing
{
public:
    void    foo() {}
};

int main(int argc, const char * argv[]) {
    FooThing x;
    DoFoo( x );
    
    return 0;
}

This compiles, and the concept verifies that the FooThing class has a method foo. But suppose I want to make the method foo private, and call DoFoo on the FooThing from another method of FooThing. So I try adding a friend declaration:

class FooThing
{
private:
    void    foo() {}
    
    friend void DoFoo<FooThing>( FooThing& thing );
};

This results in an error message: FooThing does not satisfy HasFoo because t.foo() would be invalid: member access into incomplete type FooThing.

To reassure myself that the concept really is essential to this problem, I tried doing without it,

template <typename T>
void DoFoo( T& thing )
{
    thing.foo();
}

and then the error goes away. Is there any way to fix the error while keeping the concept?


If I try the suggestion of an attempted answer and add a forward declaration

template<typename T> void DoFoo(T&);

before the class, then the compile error goes away, but I get a link error saying that DoFoo(FooThing&) or DoFoo<FooThing>(FooThing&) is an undefined symbol.

JWWalker
  • 22,385
  • 6
  • 55
  • 76
  • Somebody marked this a dupe of [this question](https://stackoverflow.com/questions/37295690/c-concept-with-friend-like-access?noredirect=1&lq=1), which is highly related but I'm not sure it's strictly speaking a dupe. Even if you could friend `DoFoo`, it wouldn't be meaningful since `HasFoo` would fail on the private access (you can't give the concept access in this way, see linked question). – Barry Jun 10 '22 at 21:18
  • @Barry That was me; was I wrong? – Paul Sanders Jun 10 '22 at 21:20
  • @PaulSanders I... am not sure. Part of the issue here is that you just can't check the concept while the class is incomplete, I don't think, so the friend declaration just doesn't work at all? That's kind of unrelated to the access (even if `void foo()` were public)? – Barry Jun 10 '22 at 21:23

2 Answers2

3

Yes! This friend declaration compiles and links:

class FooThing
{
//public:
    void    foo() {}    

    template<HasFoo T>
    friend void DoFoo(T&);
};

I would have to poke around to find the exact standardese, but I know there is a rule that multiple declarations referring to the same template must have the same constraints (typename T is not allowed here).

sigma
  • 2,758
  • 1
  • 14
  • 18
  • Doesn't compile for me, using Xcode 13.4.1 , see additions to my question for details. – JWWalker Jun 11 '22 at 16:29
  • @JWWalker Xcode is not a compiler. What is your compiler make and version? – n. m. could be an AI Jun 11 '22 at 16:53
  • Xcode comes with a built in compiler, some Apple-customized version of Clang. It says Apple clang version 13.1.6 (clang-1316.0.21.2.5). I'm downloading a newer beta of Xcode. – JWWalker Jun 11 '22 at 17:00
  • @JWWalker Apple clang is weird in more ways than one. Try stock clang. – n. m. could be an AI Jun 11 '22 at 17:14
  • I also tried it in Microsoft Visual Studio 2022, and got a compile error there too. – JWWalker Jun 11 '22 at 17:43
  • Oops, my bad, I didn't read your answer carefully enough. It's not just that you added the `template` before the friend declaration, it's that you changed `FooThing&` to `T&` in the friend declaration. With the full change, Xcode is happy. – JWWalker Jun 11 '22 at 18:01
  • Still doesn't work for me in Visual Studio 2022, but I guess that's on Microsoft, so I'll accept your answer. – JWWalker Jun 11 '22 at 18:09
  • @JWWalker: That's very odd, clang on godbolt generates the same code without error since version 10.0.0. How did they mangle it so that it doesn't support this 3 major versions later!? And can you configure Visual Studio to use clang? – sigma Jun 11 '22 at 21:23
  • 1
    To clarify: normally, Visual Studio uses the msvc compiler, and your fix does not work with that. But with Visual Studio you can optionally also install an LLVM tool set, and when I switch to that, your fix works. I’ve sent Microsoft a bug report. – JWWalker Jun 12 '22 at 03:47
0

The problem is that your concept check requires that T needs to be a complete type(because of the use of t.foo()) but at the point of the friend declaration the class FooThing is incomplete and so the concept is invalid. You can confirm this by using some other complete type in the friend declaration and the the concept will work as you intended it to. Demo

Is there any way to fix the error while keeping the concept?

As long as your check requires T to be a complete type this friend declaration involving FooThing can't work as at the point of the friend declaration the class in not complete yet.

Jason
  • 36,170
  • 5
  • 26
  • 60
  • This compiles but doesn't link for me, see additions to my question for details. – JWWalker Jun 11 '22 at 16:30
  • @JWWalker To remove the linker error just provide the definition for the function template. For this just add the body in the forward declaration `{}`. See my updated answer, there is no linker error there anymore. See [demo](https://wandbox.org/permlink/DFB7E9qrEq9fOJQg) where there is no linker error. You just need to add the body `{ }` of the function template to get rid of the linker error. – Jason Jun 11 '22 at 16:32
  • @JWWalker See [this demo](https://wandbox.org/permlink/KpJnbcf3uv029EZ4) also where there is no linker error. Notice in the linked demo, we just added the body of the function template using `{ }` and the linker error is gone because now the linker can find the definition. The same `{}` is also added in my answer so that there is no linker error. Also there is no need to edit your question to add the suggestions from the answers. Instead you can ask for any clarification directly in the comment section as you have done already. – Jason Jun 11 '22 at 16:41
  • "the compiler doesn't know" yes it does, there is a declaration right above your inserted declaration. – n. m. could be an AI Jun 11 '22 at 16:46
  • When you add `{}`, I don't think it's a forward declaration any more, it's just a different template. And yes, it compiles and links, but it continues to compile and link even if I remove the `foo` method from `FooThing`. So you've defeated the purpose of my concept check. – JWWalker Jun 11 '22 at 16:49
  • It is NOT a non-type template parameter. It is a type template parameter using the concept syntax. – n. m. could be an AI Jun 11 '22 at 16:50
  • @n.1.8e9-where's-my-sharem. Yes, my mistake in saying my last comment. I take it back. I am editing and rethinking what can be done to make it work the way it was intended to do. – Jason Jun 11 '22 at 16:52
  • @n.1.8e9-where's-my-sharem. I have updated my answer to explain the cause of the error. Check out the updated answer and am open to a review/criticism if/when anything is wrong. – Jason Jun 11 '22 at 17:21
  • @JWWalker The problem is that at the point of the friend declaration the class `FooThing` is incomplete and so the check `t.foo()` doesn't pass the test and is invalid. So as long as you have a friend declaration involving incomplete type this can't work. I have added the same in my answer. – Jason Jun 11 '22 at 17:23