Core Name Lookup

Friends and where to find them

(The title is inspired with the title of one of the papers for C++ – declaration and where to find them – author: S. Davis Herring, link in the Bibliography)

Have you ever liked something? Strong enough to make a friendship with such an entity? If the answer is yes, then that post is just for you. Also, I created this post because of some compilation error, that occurred during compilation of one of the boost libraries.

Friendship, as any other relationship is, of course, tough and you need to spend a lot of time understanding another entity, so let’s talk about friendship from the beginning.

The beginning of the friendship

So let’s have a look at a very innocent relationship as below – non template entity friends another non-template entity. Also because it’s more fun, we will mostly talk about friendships of classes and functions. So:

void foo();

struct S{
  friend void foo();  
};

such a friendship is boring as hell, whatever I will tell you about that, you already know. Basically, foo gets access to all private members of the structure S. Nothing fancy, let’s continue, then. Let’s say you forget to declare the function foo outside of the S, like so:

struct S{
  friend void foo();  
};

What happens if you try to compile the code? Well… it will compile. This might be the first time, that friendship surprises us. What is actually happening in our code? Well, this is the friend declaration.

It declares the function foo if it didn’t exist before (if it did exist, then it redeclares one). This declaration has some limitations comparing to the regular one, but we will not talk about that. The compiler will tell you this when the time comes.

So if you declared such function, and let’s say you have got definition in a different translation unit, can you call such a function? This is a good question. So let’s get a level deeper into that:

What does such a function declare? Well, it declares a function, of course, the question is, where is that function declared? And here it gets tricky. The function declared in such a way has linkage of a namespace it’s declared in and what’s important, it is not a member of a class it’s declared in.

int main(){
  foo(); // nope the function is not declared
  ::foo(); // nope - the function is not declared in the global namespace
  S::foo(); // still no - foo is not a member of S!
  using S::foo; // cannot using-declare member!

  
  void foo(); // declaration of the same function as in S structure
  foo(); // aah there you are
}

Maybe it’s possible to call it from within a class?

struct S{
  friend void foo();
  void bar(){foo();} // still no - compiler is not looking for a non-members in S
};

So it’s not possible to call that function. The only thing capable of calling it is an ADL (Argument Dependent Lookup) – if an argument of some type is passed to a function, then the function will be also looked for in type itself and its namespace. Let’s modify our example a bit:

struct S{
  friend void foo(const S&){std::cout << "you found me!" << std::endl;}  
};

int main(){
  S s;
  foo(s);
}

First of all, the program prints “you found me” but there’s more to that. You can not only declare, but also define a friend function inside the structure S. Such definition is inline (since it’s in the scope of the class type), and there’s no way to change that (in the C++20 world of modules, the in-class definitions are no longer implicitly inline as long as it’s not a global module).

This technique is commonly known as hidden friends – you cannot find the name differently than by ADL.

Well there is another strangeness, that you might see:

struct S{
  static void foo(const S&){std::cout << "missed" << std::endl;}
  friend void foo(const S&){std::cout << "you found me!" << std::endl;}
};

It just feels strange, but after a closer look, it’s not at all. We declared two different types of beings: function and member function. They have got totally different linkages (one belonging to the structure S, the second one to the enclosing namespace), so they can coexist with each other.

Making friendship spicey – adding templates

So since we know that we can start adding templates to the fun. So we can add templates. First of all – let’s add template to the structure:

template <typename T>
struct S{
  friend void foo(const S&){std::cout << "you found me!" << std::endl;}
};

What do we have here? Class template S and hidden friend foo (again, it’s hidden because you cannot call it like a normal function, what we’ve shown above). The first question appears: what is foo? Well, it’s a function (specifically it’s not a function template). How to check that? Let’s declare it and modify an example a bit.

template <typename T>
struct S{
  friend S foo(){std::cout << "you found me!" << std::endl; return S{};}
};

S<int>& foo();

int main(){
  foo(); // linker fails ;()
}

What’s happened? since, there is no instantiation of S the implementation of foo also doesn’t exist, even though it’s not a function template. Why it’s implementation doesn’t exist if it’s not a function template? It’s because it’s still a templated entity and this is how they behave.

Ok, so let’s make it compile:

template <typename T>
struct S{
  friend S foo(){std::cout << "you found me!" << std::endl; return S{};}
};

S<int> foo();

template class S<int>; //explicit instantiation

int main(){
  foo(); // prints you found me
}

ok, now we are sure it’s just a function, not a function template. What if we wanted to friend specialization of the function template foo?

The simplest way would be to declare such function template before the S like so:

template <typename T>
struct S; // forward declaration

template <typename T>
S<T>& foo();

template <typename T>
struct S{
  friend S& foo<>() {std::cout << "you found me!" << std::endl;}//only specialization is friended
};

done now foo refers to function template (I know, there is missing return – it’s not really needed for us right now). Let’s try it:

int main(){
  foo<char>(); // linker error - S<char> not instantiated
}

let’s make it compile:

int main(){
  S<char> a;
  foo<char>(); // prints you found me / compiles fine
}

can you already spot that pathological part of the friendship? No? It’s not strange, after all, it’s difficult to spot issues in the entity you like! But here’s an issue. Whether or not the function template is defined right now depends on whether or not a specialization of S for a given type is done. We can treat that as a compiler state. Now the funny thing is, that we actually can query that state (check whether the function is defined or only declared) with SFINAE. If you realize you can query that kind of state and based on that do different stuff (including modification of such state), you can start thinking about stuff like compile-time counters etc. That technique is a base for that implementation: http://b.atch.se/posts/constexpr-counter/ . Even though the code under link no longer compiles, it doesn’t seem to be against C++ rules (there is even C++ Defect Report, that we allow such “arcane” stuff)

Stateful metaprogramming via friend injection

Section: 17.7.5  [temp.inject]     Status: open     Submitter: Richard Smith     Date: 2015-04-27

Defining a friend function in a template, then referencing that function later provides a means of capturing and retrieving metaprogramming state. This technique is arcane and should be made ill-formed.

Notes from the May, 2015 meeting:

CWG agreed that such techniques should be ill-formed, although the mechanism for prohibiting them is as yet undetermined.

http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html

Ok, let’s go further with our friendship, and let’s say we fully want to have our template declaration and definition inside the struct. No problem. That’s how you do it:

template <typename T>
struct S{
  template <typename U>
  friend void foo(const S<U>&){std::cout << "you found me!" << std::endl;}
};

Good? Kinda. It works… sometimes. It tends to complain sometimes as friends usually do. Let’s have a look:

int main(){
  S<char> a;
  foo(a); // yay - works!
  S<int> b; // redefinition - what?
}

Yes, redefinition. How is that? …

Quick recap: friend function belongs to the enclosing namespace + each instantiation of S instantiates another implementation of function template foo, but since all those function templates have the same declaration, every other instantiation is a redefinition of the first instantiation. We can spot that on a much simpler example:

struct S{
  friend void foo(){std::cout << "hello" << std::endl;}  
};

struct T{
  friend void foo(){std::cout << "hello" << std::endl;}  //redefinition  
}

But in this example, it makes no sense (there is no way to call foo, since it’s not declared).

And this was also a bug in boost I mentioned at the beginning. Let’s have exactly a look at what was an issue:

template <typename NextLayer>
class ssl_stream{
  // ...
  template<class SyncStream>
  friend void teardown(
                boost::beast::role_type role,
                ssl_stream<SyncStream>& stream,
                boost::system::error_code& ec)
  {
    // ...
  }
};

Such a friend function template was introduced. With every second instantiation of ssl_stream, there was a redefinition error.

The solution was, that instead of the inline definition of the template, only the declaration was provided and the definition was moved outside of the ssl_stream template:

template <typename NextLayer>
class ssl_stream{
  // ...
  template<class SyncStream>
  friend void teardown(
                boost::beast::role_type role,
                ssl_stream<SyncStream>& stream,
                boost::system::error_code& ec); //declaration
};

template<class SyncStream> //definition
void teardown(
       boost::beast::role_type role,
       ssl_stream<SyncStream>& stream,
       boost::system::error_code& ec){
  //impl
}

But is that the proper fix? I don’t believe so. It will work but semantically it’s not what we want to achieve. Our goal is to give a function teardown access to the internals of ssl_stream, but instead, we give any specialization of teardown template access to the internals. In practice, it doesn’t matter much, but unfortunately, the code doesn’t represent the intention. Also, such a solution would be way simpler:

template <typename NextLayer>
class ssl_stream{
  // ...
  // no template here
  friend void teardown(
                boost::beast::role_type role,
         	ssl_stream& stream, // ssl_stream will refer to the current specialization
         	boost::system::error_code& ec);
};

There was also another function with the same issue:

template <typename NextLayer>
class ssl_stream{
  // ...
  template<class AsyncStream, class TeardownHandler>
  friend void async_teardown(
                boost::beast::role_type role,
                ssl_stream<AsyncStream>& stream,
                TeardownHandler&& handler)
  {
    //...
  }
};

The things might seem more complicated, but it’s not that much at all. Right now our friended function template has two template parameters. We cannot reduce that to stop being a template, but still, a simple inline definition would do the job:

template <typename NextLayer>
class ssl_stream{
  // ...
  template<class TeardownHandler>
  friend void async_teardown(
                boost::beast::role_type role,
                ssl_stream& stream, //refers to "current" specialization
                TeardownHandler&& handler)
  {
    //...
  }
};

This way, even though each time instantiation of ssl_stream happens to emit template definition, each time it’s a different template, so no redefinition error will occur.

Summary

Friendship in C++ is a powerful tool and like any other tool in C++ it can surprise with its behavior (or maybe it’s templates that are surprising, rather than friendship?).

Its behavior can especially be confusing when combining with templates, as a fact, confusing rules of C++ cause bugs even in so popular libraries as a boost (and the bug was there as of boost 1.70.0).

Use it with care and stay healthy! ( #stayathome )


Bibliography

6 thoughts on “Friends and where to find them”

  1. Friend declaration also surpasses name hiding:


    void foo(double) { cout << "foo(double)" << endl; }
    void foo(int) { cout << "foo(int)" << endl; }

    struct S {
    friend void foo(double);

    S()
    {
    foo(1); // foo(int)
    foo(1.0); // foo(double)
    }
    };

    In 'struct S', the friend declaration appears to be in class scope but then it should hide overloaded file scope definitions (which it apparently does not).

    Contrast this to:


    void F()
    {
    void foo(double);
    foo(1); // foo(fouble)
    foo(1.0); // foo(double)
    }

    MS-VC 2017 if that matters…

      1. It has! It is a function declaration, if there wasn’t one before or redeclaration of one. See: http://eel.is/c++draft/class.friend#4
        Just like redeclaration in the F() function.

        The declaration in @Risto’s example indeed won’t influence the lookup, while redeclaration in F() does.

        The reason for that, is when we say “foo” in the S constructor we are saying: look for the member names foo in S and then regular functions. The foo is not the member, so the name lookup doesn’t pick it up.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.