Design issue : "self type" as a default template argument (recursive template arguments)

Discussion in 'C++' started by IR, Nov 21, 2006.

  1. IR

    IR Guest

    Hi,

    I've been trying to do the following (which doesn't compile) :

    template<class T, class F = Example<T> >
    struct Example
    {
    F foo();
    };

    which gives me a "recursive type or function dependency context too
    complex" error (I don't really understand _why_).

    Trying to change the declaration to :

    template<class T, class F = Example<T,F> >
    struct Example
    {
    F foo();
    };

    just leads me to a "'F' : undeclared identifier" error (which I
    fully understand, contrary to the previous error).



    The goal I'm trying to achieve is to be able to write both

    typedef Example<SomeClass> SomeExample;

    and

    struct OtherExample : public Example<OtherClass,OtherExample>
    {
    //... whatever
    };

    SomeExample::foo should have a return type of Example<SomeClass>
    (aka. SomeExample due to the typedef), while OtherExample::foo
    should have a return type of OtherExample.



    I've already done things like these, but without template default
    argument, as the base template was then designed to be inherited
    from (ctors protected, ...). But in this case, the base template
    (Example) can be used "standalone".


    The scary part with this one, is that without Example's default
    template argument (F), I wouldn't be able to write

    typedef Example<SomeClass, Example<SomeClass, ... > > SomeExample;

    due to the recursive nature of the declaration.


    So I guess that I must really be doing something wrong.


    The only solution I've come up with is something along the lines:

    class SelfType {};

    template<class T, class F = SelfType>
    struct Example
    {
    private:
    template<class U> struct helper { typedef U Type; };
    template<> struct helper<SelfType> { typedef Example<T> Type; };
    typedef typename helper<F>::Type FinalType;
    public:
    FinalType foo();
    };

    This seems to work as far as I tested it, but I'm not satisfied
    mainly due to the "scary part" I mentionned (which leads me to
    believe I'm kinda playing with Evil).

    Do you think I am missing something?


    --
    IR
    IR, Nov 21, 2006
    #1
    1. Advertising

  2. Re: Design issue : "self type" as a default template argument (recursivetemplate arguments)

    * IR:
    > Hi,
    >
    > I've been trying to do the following (which doesn't compile) :
    >
    > template<class T, class F = Example<T> >
    > struct Example
    > {
    > F foo();
    > };
    >
    > which gives me a "recursive type or function dependency context too
    > complex" error (I don't really understand _why_).


    Try do do the compiler's job. The definition of F is (filling in the
    default) Example<T, F>, which is Example<T, Example<T, F> >, which is...


    [snip]
    > typedef Example<SomeClass> SomeExample;
    >
    > and
    >
    > struct OtherExample : public Example<OtherClass,OtherExample>
    > {
    > //... whatever
    > };
    >
    > SomeExample::foo should have a return type of Example<SomeClass>
    > (aka. SomeExample due to the typedef), while OtherExample::foo
    > should have a return type of OtherExample.


    So what's "OtherClass" used for, then?

    One solution may be to split the single template definition into a
    general one and a specialization.

    However, with the vague specification (e.g. "OtherClass") it's difficult
    to give an example that wouldn't probably be misleading.

    --
    A: Because it messes up the order in which people normally read text.
    Q: Why is it such a bad thing?
    A: Top-posting.
    Q: What is the most annoying thing on usenet and in e-mail?
    Alf P. Steinbach, Nov 21, 2006
    #2
    1. Advertising

  3. IR

    IR Guest

    Alf P. Steinbach wrote:
    > So what's "OtherClass" used for, then?
    >
    > One solution may be to split the single template definition into a
    > general one and a specialization.
    >
    > However, with the vague specification (e.g. "OtherClass") it's
    > difficult to give an example that wouldn't probably be misleading.


    Indeed, I guess I over-simplified the example. My intent was to
    extract only the syntactic problem I was running into, but I lost
    the global design on the way. So I'll try to be more precise...


    I currently have a template class (Example in my previous post)
    which acts as a specialized container of objects (T, SomeClass,
    OtherClass, ...), using a std::map as its underlying implementation.
    For simplicity, I'll pretend that it is a std::map<unsigned,int>, and
    get rid of the useless template parameters (policies).

    It's member methods almost always return either a reference to itself,
    or a modified copy. Most of these methods are symmetrical.


    So it could be written:

    template< /*original arguments ommitted*/ >
    class Base
    {
    public:
    Base();
    Base(const Base&);
    Base& operator =(const Base&);

    Base& foo(int i)
    {
    // do whatever with i and map_
    return *this;
    };

    Base bar(int i) const
    {
    Base tmp(*this);
    tmp.foo(i);
    return tmp;
    };
    private:
    std::map<unsigned,int> map_;
    };

    FYI foo/bar is actually about 20 pairs of symmetric overriden
    functions (FinalType& fn1() / FinalType fn1() const, ...).


    Due to the number of template arguments (6), this template class is
    _always_ used through a typedef (which in reality often spans over 6-
    10 lines, so without typedef the code would be unreadable):

    typedef Base< /*...*/ > Derived1;
    typedef Base< /*...*/ > Derived2;
    etc...

    So Derived1::bar returns a Derived1 (aka. Base</*...*/> due to the
    typedef), while Derived1::foo returns a Derived1&.


    But I now feel the need to add an optional rule enforcer to some of
    the derived classes (let's say to Derived2) so I thought I'd do the
    following :

    template</*...*/>
    class Base
    {
    public:
    Base(); // rules_ is initialized to NULL
    Base(const Base&);
    Base& operator =(const Base&);
    ~Base();

    Base& foo(int i)
    {
    // do whatever with i and map_
    if (rules_)
    rules_->Enforce_foo(i,map_);
    return *this;
    };

    Base bar(int i) const
    {
    Base tmp(*this);
    tmp.foo(i);
    return tmp;
    };
    protected:
    struct RulesInterface
    {
    virtual void Enforce_foo(int, std::map<unsigned,int>&) const = 0;
    };
    Base(const RulesInterface* rules)
    // Initialize just like Base(), plus,
    rules_(rules)
    {
    };
    private:
    std::map<unsigned,int> map_;
    const RulesInterface* rules_;
    };

    so I still can write
    typedef Base</*...*/> Derived1;

    but also

    class Derived2 : public Base</*...*/>
    {
    public:
    Derived2() : Base</*...*/>(&myRules_), myRules_() {};
    Derived2(const Derived2&);
    Derived2& operator =(const Derived2&);
    private:
    struct MyRules : public Base</*...*/>::RulesInterface
    {
    virtual void Enforce_foo(int i, std::map<unsigned,int>& m) const
    {
    // do whatever with i and m
    };
    };
    const MyRules myRules_;
    };


    A few things about the above code :

    1) even though Derived2 passes a pointer to myRules_ while it isn't
    yet constructed, it's (more or less) ok since Base doesn't refer to it
    in it's ctors nor in it's dtor.

    2) any Base derivative is "final" by design (it would make no sense to
    derive again from DerivedN).

    However the problem with that new version of Base/Derived2 is that the
    existing codebase expects Derived2 to return Derived2 objects (or
    Derived2& depending on which method is called).


    Here I have two choices:

    a) either override Base's methods in Derived2 so that they return
    Derived2 instead of Base, which means code duplication = The Root Of
    All Evil IMO, even worse than what's lying below...

    b) or, tweak Base so that it can return a Derived2 object if needed
    (which was the reason of my original post).


    So now I have my Base template looking like this:

    class SelfType{};

    template</*... ,*/ class F = SelfType>
    class Base
    {
    template<class U>
    struct helper { typedef U Type; };
    template<>
    struct helper<SelfType> { typedef Base</*...*/> Type; };
    typedef typename helper<F>::Type FinalType;
    public:
    Base(); // rules_ is initialized to NULL
    Base(const Base&);
    Base& operator =(const Base&);
    ~Base();

    FinalType& foo(int i)
    {
    // do whatever with i and map_
    if (rules_)
    rules_->Enforce_foo(i,map_);
    return static_cast<FinalType&>(*this);
    };

    FinalType bar(int i) const
    {
    FinalType tmp(*this);
    tmp.foo(i);
    return tmp;
    };
    protected:
    struct RulesInterface
    {
    virtual void Enforce_foo(int, std::map<unsigned,int>&) const = 0;
    };
    Base(const RulesInterface* rules)
    // Initialize just like Base(), plus,
    rules_(rules)
    {
    };
    private:
    std::map<unsigned,int> map_;
    const RulesInterface* rules_;
    };


    The advantage of this solution is that it doesn't break any old code
    (namely, typedef's with F defaulted to SelfType), while still allowing
    me to write Derived2 just like I wrote it above, without additional
    junk.

    The disadvantage is that IMHO this implementation is plain ugly, so we
    come to the very reason of my previous post: "do you think there is a
    better design?"

    I hope the simplified code I just posted isn't too messy, but there
    might be a few typos left from the simplification of my original code.

    --
    IR
    IR, Nov 22, 2006
    #3
  4. IR

    IR Guest

    Oh well, while double-checking my post for typos, one word I wrote
    jumped to my face:

    IR wrote:
    > ...(policies).


    So I figured out the solution: just add another policy as a template
    parameter which default to a "do-nothing" policy...

    struct UncheckedPolicy
    {
    static inline
    void Enforce_foo(int i, std::map<unsigned,int>& m) const
    {
    };
    };
    struct Derived2Policy
    {
    static /* inline? depending on the complexity */
    void Enforce_foo(int i, std::map<unsigned,int>& m) const
    {
    // do whatever with i and m
    };
    };


    template< /*... ,*/ class Rules = UncheckedPolicy >
    class Base
    {
    public:
    Base();
    Base(const Base&);
    Base& operator =(const Base&);

    Base& foo(int i)
    {
    // do whatever with i and map_
    Rules::Enforce_foo(i,map_);
    return *this;
    };

    Base bar(int i) const
    {
    Base tmp(*this);
    tmp.foo(i);
    return tmp;
    };
    private:
    std::map<unsigned,int> map_;
    };

    typedef Base< /*...*/ > Derived1;
    typedef Base< /*... ,*/ Derived2Policy > Derived2;


    This solution has many performance / scalability / type safety /
    readability advantages over my previous post, with no (obvious)
    drawback.

    I was indeed missing something! I can be so blind sometimes...


    I'd still be glad to hear about what you think of both designs (my
    previous post and this one).

    Thanks for your attention anyway. :)

    --
    IR
    IR, Nov 22, 2006
    #4
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Steven T. Hatton
    Replies:
    2
    Views:
    429
    Steven T. Hatton
    Oct 6, 2004
  2. Edward Diener
    Replies:
    14
    Views:
    4,885
    Josiah Carlson
    Apr 6, 2004
  3. tutmann
    Replies:
    4
    Views:
    428
  4. nw
    Replies:
    0
    Views:
    301
  5. avasilev
    Replies:
    2
    Views:
    504
    avasilev
    Oct 6, 2011
Loading...

Share This Page