1

NOTE: this post is different from this one: Declare non-template friend function for template class outside the class, so please read my question before marking it as duplicate.

I want to declare a non-template friend function inside a class template, and the arguments and return type of that friend function is unrelated to the template argument. How should I do that?

Please note it is different from that previous question because in that question, arguments and return type of that friend function is related to the template argument.

Example, adapted from that question above:

// class.h
#include <iostream>
using namespace std;

template <typename T>
struct B
{
    T value;
    int value2;
    B() : value2(1) {}
    friend void modify(const int&); // unrelated to T!
    void printValue2() {
        modify(value2);
        cout << value2 << endl;
    }   
};

// define friend function foo() in a class.cpp file, not in the header
void modify(const int &v) { v = v * 2 + 1; } // HOW should I write it?

// main.cpp
int main() {
   B<int> b;
   b.printValue2();
   return 0;
}

I know I can declare modify() outside this template class so it becomes a vanilla, ordinary function. But I want only this template class to have access to modify(). Alternatively, to achieve this goal of access control, I could define modify() to be a static method in this template class, but that would make the method a template method, forcing me to define it in the header.

Followup: if the friend approach above doesn't work, how should I achieve the two goals at the same time:

  • access control: only that class template can access modify()
  • be able to define modify() in a *.cpp file, rather in a header.

Accepted Answer:

To achieve the two goals above, don't abuse friendship.

The best practice is let the class template privately inherit a non-template base class, and in that base class declare common non-template methods that are unrelated to template arguments.

Therefore, you are able to define these methods in a separate *.cpp file, reducing the header's size.

Leedehai
  • 3,660
  • 3
  • 21
  • 44
  • If `modify` is going to be modifying a `B` then it is going to have to be a template. Since you need to make it a template and put it in a header file you might as well make it a static or non static member function. – NathanOliver Aug 09 '18 at 21:59
  • @NathanOliver from `modify()`'s point of view, it doesn't care if it is able to modify a `B` object - it just modifies whatever integer that is passed in reference. Does this give me some leeway? – Leedehai Aug 09 '18 at 22:03
  • @Walter yes, but I want only `B` to have access to `modify()`. If I define `modify()` to be a method, it will become templated. I don't want it to be templated. – Leedehai Aug 09 '18 at 22:05
  • @Walter I disagree. Yes, friendship controls access - it should be used that way in normal cases. However, if I don't use friendship, `modify()` would either have to be declared outside the class template (causing it to be callable by others) or have to be a member method (causing it to be templated). – Leedehai Aug 09 '18 at 22:10
  • @Leedehai Okay, I now think I understand your question (it could have been phrased clearer ...). I have withdrawn the close vote and my comments. – Walter Aug 09 '18 at 22:23
  • Avoid `using namespace std;` especially in header file. – Jarod42 Aug 09 '18 at 22:44

3 Answers3

3

You might use private inheritance instead of friendship:

// class.h
#include <iostream>

class B_helper
{
protected:
    static void modify(int &v);
};

template <typename T>
struct B : private B_helper
{
    T value;
    int value2;
    B() : value2(1) {}

    void printValue2() {
        modify(value2);
        std::cout << value2 << std::endl;
    }   
};

// class.cpp
void B_helper::modify(int &v) { v = v * 2 + 1; }
Jarod42
  • 203,559
  • 14
  • 181
  • 302
0

You do it like this:

// class.cpp
void modify(const int &v) { v = v * 2 + 1; }

You are effectively abusing friendship, but ok. What this means is that you need to work around what it means to declare a function with friend: It is only visible through ADL! Now there is no way to refer to modify, because modify doesn't depend on B, so B's scope is never searched for a function named modify.

There is a work-around, but it's not pretty. You need to declare modify in every function where you use it. You could also declare it in global scope, but then everyone can call it. Alternatively, you can always declare it in a detail namespace (but this has the same issue a bit):

template<typename T>
void B<T>::printValue2() {
    void modify(const int&);
    modify(value2);
    cout << value2 << endl;
}
Rakete1111
  • 47,013
  • 16
  • 123
  • 162
  • But I want some methods template class to be able to call `modify()`. Making it static in the cpp file would defeat that purpose, because template class's methods are defined in the header. no? – Leedehai Aug 09 '18 at 22:17
  • @Leedehai Wait, why do you want to define `modify` in a .cpp file then? Like if your whole class's definition is in the header, one more function doesn't hurt. I'll update my answer. – Rakete1111 Aug 09 '18 at 22:18
0

As I said in the comments, friendship controls access to a class. As long as your function modify() is a standalone function, it cannot be befriended. As you want to call it from a template, it cannot be hidden it in a .cpp file either, but must be visible with the definition of the class template B and its member using modify().

One solution is to put modify as a static method in a auxiliary non-template class, which in turn befriends the template B<>.

// file foo.h (header)

namespace foo {
    template<typename> class B;            // forward declaration

    class exclusive_for_B
    {
        template<typename T>
        friend class B<T>;

        static void modify(int&x)          // must take int&, not const int&
        { x &= x+42; }
    };

    template<typename T>
    class B
    {
        int val;
     public:
        ...
        void printvalue()
        {
            exclusive_for_B::modify(val);  // access via friendship
            std::cout << val << '\n';
        }
    };
}
Walter
  • 44,150
  • 20
  • 113
  • 196