Can a friend of A<T> be also a friend of A<A<T>>?
Asked Answered
B

2

15

Consider the following code:

#include <vector>

template<typename T> class Container;
template<typename T> Container<Container<T>> make_double_container(const std::vector<std::vector<T>>&);

template<typename T>
class Container {
    std::vector<T> v;
    friend Container<Container<T>> make_double_container<T>(const std::vector<std::vector<T>>&);

public:
    Container() {}
    explicit Container(std::vector<T> v) : v(v) {}
};

template<typename T>
Container<Container<T>> make_double_container(const std::vector<std::vector<T>>& v) {
    Container<Container<T>> c;
    for(const auto& x : v) {
        c.v.push_back(Container<T>(x));
    }
    return c;
}

int main() {
    std::vector<std::vector<int>> v{{1,2,3},{4,5,6}};
    auto c = make_double_container(v);
    return 0;
}

The compiler tells me that:

main.cpp: In instantiation of 'Container<Container<T> > make_double_container(const std::vector<std::vector<T> >&) [with T = int]':
main.cpp:27:37:   required from here
main.cpp:8:20: error: 'std::vector<Container<int>, std::allocator<Container<int> > > Container<Container<int> >::v' is private
     std::vector<T> v;
                    ^
main.cpp:20:9: error: within this context
         c.v.push_back(Container<T>(x));

Which I believe to be correct, because make_double_container is friend of Container<T>, but not of Container<Container<T>>. How can I make make_double_container work in this situation?

Biebel answered 1/11, 2015 at 16:7 Comment(1)
Question is theoretically interesting. But vectors of vectors are, sort of, evil, so it defeats the purpose mostly.Nibelung
I
10

Clearly, you can make every specialization of make_double_container a friend:

template <typename U>
friend Container<Container<U>> make_double_container(const std::vector<std::vector<U>>&);

If you want to keep friendship to a minimum without partial specialization or the like, try

template <typename> struct extract {using type=void;};
template <typename U> struct extract<Container<U>> {using type=U;};
friend Container<Container<typename extract<T>::type>>
     make_double_container(const std::vector<std::vector<typename extract<T>::type>>&);

Demo.

Intertexture answered 1/11, 2015 at 16:35 Comment(1)
I precisely wanted to avoid f<U> becoming friend of A<T> (first code-snippet). I think your second code snippet gives "precise" friendship while avoiding the hassle of writing many specialised f's. Clever trick, +1!Biebel
K
3

Defining make_double_container as a template function of S seems to make it compile and work.

template<typename T>
class Container {
    std::vector<T> v;

    template<class S>
    friend Container<Container<S>> make_double_container(const std::vector<std::vector<S>>&);

public:
    Container() {}
    explicit Container(std::vector<T> v) : v(v) {}
};

http://coliru.stacked-crooked.com/a/bdc23a0451a2125b

when the compiler sees something in the structure of :

template<class T>
class X{};

when you specify what T is, it instanciates the class and replaces everything with the typename T to the specified type.

when you write

 Container<Container<T>> c;

T is in fact Container<T>, and make_double_container turnes into

Container<Container<Container<T>>> make_double_container(const std::vector<std::vector<Container<T>>>&); 

and then (inside main) into

Container<Container<Container<int>>> make_double_container(const std::vector<std::vector<Container<int>>>&); 

by changing the friendship into :

 template<class S>
    friend Container<Container<S>> make_double_container(const std::vector<std::vector<S>>&);

you force the compiler to figure out what is S from the template of Container<Container<T>> , and then it figures out the right type of S, which is int

Kanara answered 1/11, 2015 at 16:28 Comment(1)
Your solution would work, but it would make e.g. make_double_container<std::string> a friend of Container<Container<int>>.Biebel

© 2022 - 2024 — McMap. All rights reserved.