Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

crtp and type visibility

I have this puzzle which I am trying to solve, and fundamentally it boils down to the following example:

template <typename CT>
struct A
{
  typedef typename CT::VALUE_T FOO; // FOO is dependent on CT
};

template <typename CT>
struct B
{
  typedef typename CT::BAR BAR;

  BAR foo() {}
};

template <typename DT>
struct C : B<C<DT> >
{
  typedef DT VALUE_T;

  typedef typename A<C>::FOO BAR;
};

int main () {
  C<int> c;
}

I can try to explain the above (I've tried about three times and deleted the text!), but basically the requirements are:

  1. C must inherit from B typed with C (exploiting CRTP), i.e. B<C<>>
  2. C is the only one that can instantiate A (i.e. A has to be typed with C)
  3. A is the only one that can define FOO (FOO is dependent on the type CT, the relationship is more complicated than that presented)

The problem (as you can see with the above code) is that the BAR type is available only within C and this is incomplete when B is instantiated, hence B does not see the BAR type of the template argument CT (C<int>). Unfortunately within B, the type BAR is used as arguments to functions, and return types (i.e. not just limited to function scope - as a result I cannot simply move the typedef to function scope).

Is there a way around this? I cannot break the above stated relationships (unless as a last resort). Presumably with c++11, I could use auto and get around the need to have the BAR typedef in B, however this is currently not yet an option.

EDIT: following on from @bitmasks' comment, some more information.

  1. The code in A and B is used in quite a few binaries in different situations, the only unique situation in this case is that C derives from B, in the other instances, C owns an instance of something derived from B.
  2. The template arguments can be changed (in A and B), as long as they can be defaulted to values which doesn't require changing existing uses of A and B. The same set of types must be available either as template parameter that is defaulted or some other mechanism.

I'm using templates here simply because I require the tight-coupling and I needed the flexibility to use the code in different situations.

Descriptions of components:

  • A is best described as a container, and FOO really is an iterator, what it contains is defined by a typedef of the template parameter
  • B is best described as a base class which contains a set of functions which are called by some components owned by an instance of C. In the previous cases, those components were passed a reference to things derived from B (and those things are also owned by C), in this instance, I'm providing a reference to C itself.

The main complication arises from accessing the container A, previously the relationship between B and C is that C has an instance of B, but now C is an instance of B - this change in semantics breaks the way types are injected into the classes.

like image 910
Nim Avatar asked Dec 06 '11 14:12

Nim


1 Answers

I think you can combat the cyclic typedef requirement with default template parameters. The following works as intended (as far as I understand your question) and leaves you (almost) all the liberties of your original code:

template <typename CT, typename CTVALUE_T = typename CT::VALUE_T>
struct A
{
    //typedef typename CT::VALUE_T FOO; // FOO is dependent on CT
    typedef CTVALUE_T FOO; // FOO is dependent on CT
};

template <typename CT, typename CTBAR = typename CT::BAR>
struct B
{
    //typedef typename CT::BAR BAR;
    typedef CTBAR BAR;

    BAR foo() {return 0;}
};

template <typename DT> struct VALUE_T__from__DT {
  typedef DT VALUE_T;
};

template <typename DT, template <class T> class _C_ > struct BAR__from__DT {
  typedef typename A<_C_<DT> , typename VALUE_T__from__DT<DT>::VALUE_T >::FOO BAR;
};

template <typename DT>
struct C : B<C<DT>, typename BAR__from__DT<DT, C >::BAR >
{
    //typedef DT VALUE_T;

    //typedef typename A<C>::FOO BAR;
};

int main () {
    C<int> c;
    int x = c.foo();
    (void)x;
    return 0;
}

The names of the helper template types are horrible, perhaps you can find better names.

The core trick is that the definition of the problematic typedefs is put in a separate meta-container (i.e. a container for traits) such that you can still do any voodoo there.

I commented the unnecessary typedefs out (but left them there so you have a better chance to figure out what I'm doing there).

Does that fit your need?

like image 166
bitmask Avatar answered Oct 15 '22 11:10

bitmask