I came to the following example of using a concept, which checks if the member function is present in the class and then using this concept in the friend function. For example, to overload operator<<
for a bunch of similar classes
#include <iostream>
#include <vector>
template<class T>
concept Sizable = requires(T a)
{
{a.size()} -> std::same_as<size_t>;
};
template<class T> requires Sizable<T>
std::ostream& operator<<(std::ostream& os, const T& a);
template<class T>
class A {
std::vector<T> storage;
public:
size_t size() const { return storage.size();}
friend std::ostream& operator<< <A<T>>(std::ostream& os, const A<T>& a);
};
template<class T> requires Sizable<T>
std::ostream& operator<<(std::ostream& os, const T& a)
{
for (size_t i = 0; i < a.size(); ++i) {
}
return os;
}
int main()
{
A<int> a;
operator<< <A<int>>(std::cout, a);
}
However this code fails to compile for the reason that we have a member access into incomplete type.
Is there any way how this can be achieved, taking into account that we cannot forward declare concepts?
If the friend definition is moved inside a class then the code compiles and works.
Concepts can't do that. They are part of the function template's declaration, and so are checked when you befriend the specialization for A<T>
. The problem with that is that at the point of the friend
declaration, the class is not considered completely defined. As a matter of fact, it's considered completely defined inside its own definition only in very specific places:
The friend declaration is none of those. So to refer to the class there is to refer to an incomplete type. The concept is not part of the class, so it cannot "see" the "previously declared" parts. And once you instate A<T>
and the friend declaration along with it, the concept must be verified since a declaration of operator<< <A<T>>
is instantiated. Given it's applied to an incomplete type, the concept check fails.
Even if that wasn't a problem, the concept check makes the friendship largely moot. Concepts can only be satisfied if the members they examine are public and unambiguous. So a concept won't help if the friend function needs to access any private or protected members. To really keep the concept check, we will need to use only the public parts of the class... and for that we do not need to befriend anything. Indeed, getting rid of the friend declaration makes your code well-formed.
If we intend instead to use a class's private parts, then we can't check it with a concept, and must defer the checking to the instantiation of operator<<
's body like templates "normally" work. I'd consider a concept that "verifies the public interface" in that case to be moot (because we can still fail later). I'd dispense with the concept entirely in this case.