6

I would like to know if what I am aiming for is possible.

I have a class Class such that

#include<iostream>

template<class T> class Class;
template<class T, class W> Class<W> f(Class<T>& C, const Class<T>& D);

template<class T> class Class {
protected: // this could be private
    T m_t;
public:
    Class(): m_t(T()) {}
    Class(T t): m_t(t) {}
    T& getT() { return m_t; }
    template<class U, class W> friend Class<W> f(Class<U>& C, const Class<U>& D);
};

template<class T, class W> Class<W> f(Class<T>& C, const Class<T>& D)
{
    C.m_t += D.m_t;
    Class<W> R;
    std::cout << R.m_t << std::endl; // I don't want this to be possible
    return R;
}

int main()
{
    Class<int> C(42), D(24);
    std::cout << f<int, char>(C, D).getT() << std::endl;
}

But this way, f can access private/protected members of instances of Class where Class's type is not the same as f's arguments' type, like in the line

std::cout << R.m_t << std::endl;

(R is of type W, not T)

My question is this: is there a way I can define f as a friend function that has a template parameter specifying the return type (W), but can only access private/protected members of Class objects that are the same type as its arguments' type?

Edit 1: The solution submitted by @cantordust, while clean and aesthetic, doesn't work when Class and f are in a namespace, alas making it unsuitable for more general usecases. For example, if in a modification of cantordust's code namespace n starts just after the include declaration, and ends just before the main function, there will be no other way to use f than to put using n::f; in main, which, together with its implications, is inexcusable of well-written C++ code.

Edit 2: There is yet another solution: defining a member function, and optionally defining a similar regular function with the same parameters, and calling the member function from it. The code would look something like this:

// inside Class
template<class W> Class<W> f(Class& C, Class& D);
//outside Class
template<class T> template<class W> Class<W> Class<T>::f(Class<T>& C, Class<T>& D)
{ /* definition */ }

The procedure for defining the regular function is obvious.

  • you can friend a specialization of the function template instead of the whole template function – Tyker Jul 27 '18 at 11:08
  • Right. But that's where the problems come. If I do `template friend Class f(Class& C, const Class& D)` I get ther error error: invalid use of template-id `‘f’` in declaration of primary template `template friend Class f(Class& C, const Class& D);` –  Jul 27 '18 at 11:20
  • template function can't be partially specialized. i think th error may come from there – Tyker Jul 27 '18 at 11:22
  • Yes, so did I figure too. But how else could I specialize f? –  Jul 27 '18 at 11:24
  • you can wrap you function into a partially specialized struct – Tyker Jul 27 '18 at 11:25
  • That sounds like a great idea, but that would only work for the types I specialize by hand, writing a newer and newer wrapper struct for every one of them. This problem arose while writing a container and algorithms library, so my code is just an abstraction to get the point across. But I am sure you get why this suggestion wouldn't work in my case. –  Jul 27 '18 at 11:33
  • @Tyker Thank you nonetheless, if you think about it, Passer By's answer is a generalization of your idea :) –  Jul 27 '18 at 12:14
  • @user7474 I am not sure what you mean about the namespace issue. Could you please provide an example? – cantordust Jul 29 '18 at 12:39
  • @cantordust I edited my question, hope it's clearer now. –  Jul 29 '18 at 12:45
  • @user7474 You don't have to import the whole namespace, you could import just `f` by having `using N::f`;`. – cantordust Jul 29 '18 at 13:33
  • @cantordust I am well aware of that. But doing so will result in error when trying to reference f by n::f, which should be the normal behaviour expected from any class or library, not to mention the unreasonable burden of having to use `using n::f;` each time the user wants to access what is (should be) just an ordinary function. –  Jul 29 '18 at 14:00
  • Interesting. This smells like a bug, but maybe I'm missing something. It works as expected with a namespace (**without** `using N::f;`) [with GCC < 6](https://godbolt.org/g/3ufDV6). – cantordust Jul 30 '18 at 01:41
  • I posted a follow-up question about this [here](https://stackoverflow.com/questions/51586190/undefined-reference-to-friend-function-template-defined-inside-class-in-a-namesp). – cantordust Jul 30 '18 at 02:16
  • @cantordust I find the answer to your follow-up question to give closure to this problem. I want to thank you for your devotion and your commitment to professionality. –  Aug 02 '18 at 12:28

2 Answers2

3

You could indirect through a template class

template<class T> class Class;
template<typename>
struct fs;

template<class T> class Class {
protected: // this could be private
    T m_t;
public:
    Class(): m_t(T()) {}
    Class(T t): m_t(t) {}
    T& getT() { return m_t; }
    friend struct fs<T>;
};

template<typename T>
struct fs
{
    template<typename W>
    static Class<W> f(Class<T>& C, const Class<T>& D)
    {
        C.m_t += D.m_t;
        Class<W> R;
        std::cout << R.m_t << std::endl; // ill-formed
        return R;
    }
};

template<class T, class W>
Class<W> f(Class<T>& C, const Class<T>& D)
{
    return fs<T>::template f<W>(C, D);
}

Live.

The indirection is necessary since you can't befriend a partial specialization.

Passer By
  • 19,325
  • 6
  • 49
  • 96
  • Had to stare at it a little to process, but I am close to understanding now. This really solves my problem without any "holes" in it. Thank you very much! –  Jul 27 '18 at 12:12
0

Under mild assumptions, you don't need a helper struct:

#include<iostream>

template<class T> class Class;
template<typename U, typename W>
Class<W> f(Class<U>& C, const Class<U>& D);

template<class T>
class Class
{
protected: // this could be private

    T m_t;

public:
    Class()
        :
          m_t(T())
    {}

    Class(T t)
        :
          m_t(t)
    {}

    T& getT()
    {
        return m_t;
    }

    template<typename U, typename W>
    friend Class<W> f(Class<T>& C, const Class<T>& D)
    {
        C.m_t += D.m_t;
        Class<W> R;
        std::cout << R.m_t << std::endl; // I don't want this to be possible
        return R;
    }
};

int main()
{
    Class<int> C(42), D(24);
    std::cout << f<int, char>(C, D).getT() << std::endl;
}

Demo here

cantordust
  • 1,542
  • 13
  • 17
  • Quite elegant solution, thank you! It turns out that I know C++ less and less than I previously thought, but I guess that's how one learns. –  Jul 27 '18 at 21:43
  • :) I feel like that all the time, too. Glad to be of help! – cantordust Jul 27 '18 at 21:45
  • I just noticed you upgraded the solution. I actually thought of this very idea following the intuition your previous answer gave, copied the code into my IDE and realized it was already done. For a moment I thought it was some wicked magic. Anyway, I think I learned an important lesson here: don't settle for less. Thanks again! –  Jul 27 '18 at 21:53
  • `don't settle for less` - I think I'll adopt this as my motto! :) – cantordust Jul 27 '18 at 22:01