Search code examples
c++templatesdeclarationfriendincomplete-type

Class template explicit instantiation declaration


This is a bit of a complicated pattern that doesn't fit well with friendship. Maybe I have to reconsider the design, but for now I'm just interested whether it is possible to make this work. The problem is that I can't declare class A template explicit instantiation (with incomplete class B as a template argument) that I want to use in a function specialization declaration that I want to use as a friend declaration in the definition of B.

namespace ns
{
    template<class ElemT>
    void assem_elem(ElemT& elem);

    template<class CompT>
    class ElemTempl
    {
    public:
        ElemTempl()
        {
            assem_elem(*this);
        }
        CompT comp;
    };


    namespace el { class Comp; }
    template class ElemTempl<el::Comp>; // error: 'ns::ElemTempl<ns::el::Comp>::comp' uses undefined class 'ns::el::Comp'
    using Elem = ElemTempl<el::Comp>;
    template<> void assem_elem<Elem>(Elem& elem);
    
    namespace el
    {
        class Comp
        {
            friend void ns::assem_elem<Elem>(Elem& elem);
            void link(){}
        };
    }

    template<> void assem_elem<Elem>(Elem& elem)
    {
        elem.comp.link();
    }
}

int main()
{
    ns::Elem el{};
    return 0;
}

Update:

I came up with two solutions. First, I can just remove

template class ElemTempl<el::Comp>;

line at all. The next line

using Elem = ElemTempl<el::Comp>;

seems to be a declaration of the instantiation(?). Also, even without using line I can write

template<> void assem_elem<ElemTempl<el::Comp>>(ElemTempl<el::Comp>& elem);

directly and this will work. But why? I can't do this with regular classes. At least I must say something like <class RegularClass>, not just <RegularClass>.

The second solution is using a class and passing it through the element's template parameters:

namespace ns
{
    template<class CompT, class AssemT>
    class ElemTempl
    {
    public:
        ElemTempl()
        {
            AssemT{ *this };
        }
        CompT comp;
    };

    class Assem;
    namespace el
    {
        class Comp
        {
            friend ns::Assem;
            void link() {}
        };
    }
    using Elem = ElemTempl<el::Comp, Assem>;
    class Assem
    {
    public:
        Assem(Elem& elem) { elem.comp.link(); }
    };
}

But here is also some thing that needs clarification. Class Assem uses Elem, thus it instantiates Elem, but Elem needs Assem to be instantiated and Assem is not defined yet. How can this work?


Solution

  • You seems to confound explicit instantiation and template specialization.

    With template class ElemTempl<el::Comp>; (explicit instantiation), you instantiate the full class, and CompT comp; requires a complete type, whereas el::Comp is just forward declared.

    using Elem = ElemTempl<el::Comp>; is just the definition of an alias. no instantiation done.

    template<> void assem_elem<Elem>(Elem& elem); declares a specialization, Elem might be incomplete.

    Class Assem uses Elem, thus it instantiates Elem, but Elem needs Assem to be instantiated and Assem is not defined yet. How can this work?

    Elem by itself only need forward declaration to be valid.

    The class requires complete type el::Comp when instantiated.
    The constructor ElemTempl::ElemTempl requires complete Assem when instantiated.

    Constructors and methods are not instantiated when class is implicitly instantiated, but they are when the class is explicitly instantiated.