Может ли друг A <T> быть другом A <a <t>>?

Рассмотрим следующий код:

#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;
}

Компилятор сообщает мне, что:

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));

Я считаю правильным, потому что make_double_container является другом Container<T>, но не Container<Container<T>>. Как я могу сделать make_double_container работу в этой ситуации?

Ответы

Ответ 1

Ясно, что вы можете сделать любую специализацию make_double_container друга:

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

Если вы хотите сохранить дружбу до минимума без частичной специализации и т.п., попробуйте

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>>&);

Демо.

Ответ 2

Определение make_double_container как функции шаблона S, похоже, заставляет его скомпилировать и работать.

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

когда компилятор видит что-то в структуре:

template<class T>
class X{};

когда вы указываете, что такое T, он инициализирует класс и заменяет все на typename T на указанный тип.

когда вы пишете

 Container<Container<T>> c;

T на самом деле Container<T>, а make_double_container переходит в

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

а затем (внутри основного) в

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

изменив дружбу на:

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

вы вынуждаете компилятор выяснить, что есть S из шаблона Container<Container<T>>, а затем он определяет правильный тип S, который равен int