2

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.

Drew Dormann
  • 59,987
  • 13
  • 123
  • 180
tupos
  • 150
  • 8
  • 1
    Even w/o concepts, you cannot have a `friend` function of a class template defined outside the class. A non-template friend is generated for each class instantiation, which is not the same as a function template. So, the question is moot. – Eugene Nov 04 '21 at 23:03
  • 2
    @Eugene - This is a class befriending a specialization, not an attempt to define a generic non-template friend outside the class. Befriending specialization is kosher, and the question is far from moot. – StoryTeller - Unslander Monica Nov 04 '21 at 23:39

1 Answers1

1

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:

  • function bodies,
  • default arguments,
  • the noexcept specifier,
  • default member initializers.

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.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458