1

I stumbled into a scenario and I'm trying to figure out for the cleanest approach, if there's any.

I have a template class with a protected constructor, that needs to be instantiated by a friend template class. Both share part of template parameters, but not all. Here's a example of my problem.

I wish to know from experienced programmers if there are other possible solutions (I suppose not, besides turning constructor public), and if between the two I present one its more acceptable than the other. Thanks

Solution 1- I supply "unnecessary" template parameters to the class with protected constructor (class Element).

template <typename Tp_>
class Engine_Type_X
{
};
template <typename Tp_>
class Engine_Type_Z
{
};

//Forward declaration
template <typename Tp_, template<typename> typename Eng_>
class Container;

//Eng_ is only required to declare the friend class
template <typename Tp_,template<typename> typename Eng_> 
class Element
{
    friend class Container<Tp_,Eng_>;

    Tp_ tp_;
protected:  
    Element(Tp_ tp) : tp_{tp}   //protected ctor!!!
    {}
};

template <typename Tp_, template<typename> typename Eng_>
class Container
{
    using Element_tp = Element<Tp_,Eng_>;
    using Engine_tp  = Eng_<Tp_>;

    std::vector<Element_tp> container_;
    Engine_tp               &engine_;

public:
    Container(Engine_tp &engine) : container_{},engine_{engine}
    {}

    void install(Tp_ tp)
    {   Element_tp elem{tp};
        container_.emplace_back(elem);        
    }
};

Solution 2 - I use an approach like the one I've found here How to declare a templated struct/class as a friend?

template <typename Tp_>
class Engine_Type_X
{
};
template <typename Tp_>
class Engine_Type_Z
{
};

template <typename Tp_>
class Element
{
    template<typename,template<typename>typename> friend class Container; //All templated classes are friend

    Tp_ tp_;
protected:  
    Element(Tp_ tp) : tp_{tp}   //protected ctor!!!
    {}
};

template <typename Tp_, template<typename> typename Eng_>
class Container
{
    using Element_tp = Element<Tp_>;
    using Engine_tp  = Eng_<Tp_>;

    std::vector<Element_tp> container_;
    Engine_tp               &engine_;

public:
    Container(Engine_tp &engine) : container_{},engine_{engine}
    {}

    void install(Tp_ tp)
    {   Element_tp elem{tp};
        container_.emplace_back(elem);        
    }
};
Jacinto Resende
  • 343
  • 2
  • 13
  • the two have different meaning. In the first only `Container;` is friend of `Element` in the second any `Container` is friend of any `Element`, pick the one you need. – 463035818_is_not_an_ai Feb 11 '19 at 12:23
  • Possible duplicate of [partial template specialization for friend classes?](https://stackoverflow.com/questions/44213761/partial-template-specialization-for-friend-classes) – chtz Feb 11 '19 at 12:24
  • In your case an alternative might be to declare `Element` as an internal class of `Container` (not sure how that would fit into your overall design). – chtz Feb 11 '19 at 12:29
  • Use the second one. `Engine` is logicaly a `Container`'s detail, so it makes no sense to put it in `Element`'s signature. – jrok Feb 11 '19 at 12:35

1 Answers1

0

You still have some options to explore.

  1. You could make one class an inner class (called nested class), that would automaticly friend it to the 'outside' class. See https://en.cppreference.com/w/cpp/language/nested_types

  2. Another approach is to require a so called 'token' as a parameter to a the constructor, this token type doesn't usually take template parameters, then make it so that this token can only be created by the other class (could be a nested type or friended).

On request from OP, here is an outline of one way to accomplish 2. option: (using c++0x)

template<typename Test, template<typename...> class Ref>
struct is_specialization : std::false_type {};

template<template<typename...> class Ref, typename... Args>
struct is_specialization<Ref<Args...>, Ref>: std::true_type {};


template <class T>
class create_token {
    public:
    typedef T Type;
                 //copy of token not allowed !
    create_token(const create_token &) = delete; 
    create_token& operator=(const create_token &) = delete; 
                 //move is fine
    create_token(create_token &&) = default; 
    create_token& operator=(create_token &&) = default; 

    friend class T;
    private:
    create_token();
  };


template<class BlaBlaBla>
struct Element {
    template<class T>
    Element(create_token<T> t)  {
        static_assert(std::is_specialization<create_token<T>::Type, Container>::value, "Wrong type of token provided");
    }
};

template<class Whatever>
struct Container {
    template<class T>
    Element(create_token<T> t)  {
        static_assert(std::is_specialization<create_token<T>::Type, Element>::value, "Wrong type of token provided");
    }
};
darune
  • 10,480
  • 2
  • 24
  • 62
  • If I nest Element inside Container how can I access its protected constructor without explicitly declaring Container as a friend? I didn't understand your second suggestion. Could you provide a quick example, not necessarily based on my own. Thank you. – Jacinto Resende Feb 11 '19 at 14:45
  • @JacintoResende I made an outline of how to do the second option. It may contain errors, etc. but I hope you find it usefull. – darune Feb 11 '19 at 19:13