Restrict the scope of class instances accessible by multiple template parameter friend function
Asked Answered
A

2

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.

Aftermath answered 27/7, 2018 at 11:2 Comment(14)
you can friend a specialization of the function template instead of the whole template functionPotion
Right. But that's where the problems come. If I do template<class W> friend Class<W> f<T, W>(Class<T>& C, const Class<T>& D) I get ther error error: invalid use of template-id ‘f<T, W>’ in declaration of primary template template<class W> friend Class<W> f<T, W>(Class<T>& C, const Class<T>& D);Aftermath
template function can't be partially specialized. i think th error may come from therePotion
Yes, so did I figure too. But how else could I specialize f?Aftermath
you can wrap you function into a partially specialized structPotion
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.Aftermath
@Potion Thank you nonetheless, if you think about it, Passer By's answer is a generalization of your idea :)Aftermath
@user7474 I am not sure what you mean about the namespace issue. Could you please provide an example?Frontage
@Frontage I edited my question, hope it's clearer now.Aftermath
@user7474 You don't have to import the whole namespace, you could import just f by having using N::f;`.Frontage
@Frontage 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.Aftermath
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.Frontage
I posted a follow-up question about this here.Frontage
@Frontage 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.Aftermath
B
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.

Bibelot answered 27/7, 2018 at 11:28 Comment(1)
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!Aftermath
F
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

Frontage answered 27/7, 2018 at 21:36 Comment(4)
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.Aftermath
:) I feel like that all the time, too. Glad to be of help!Frontage
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!Aftermath
don't settle for less - I think I'll adopt this as my motto! :)Frontage

© 2022 - 2024 — McMap. All rights reserved.