4

I'm working on an iterator family, where all iterator classes X have X& operator++() {++x; return *this} in common, so it seems to be a good idea to place it in a common base class. Unfortunately, the return type changes, since it should always return a reference to the derived class.

The following illustrates the problem. I would like f() to work, but the only workarounds I could come up with are g() and h(), which are not satisfactory:

struct A {
    A& f() {return *this;}

    template <typename T>
    T& g() {return *(T*)this;}

    template <typename T>
    T& h(T& unused) {return *(T*)this;}
};

struct B : A {
    int x;
    B(int x) : x(x) {}
};

int main() {
    B b(12);

    //B b1 = b.f();   // error: conversion from 'A' to non-scalar type 'B' requested

    B b2 = b.g<B>();  // works
    B b3 = b.h(b);    // works
}

Is there a way to make B b1 = b.f(); work? Maybe using C++11 features?

Stefan
  • 4,380
  • 2
  • 30
  • 33
  • Why is g() unsatisfactory? – rozina Jun 10 '14 at 09:50
  • @rozina: Eventually, it should work for `operator++()`, so I can't use additional template arguments. `h()` would be fine for any binary operators, but not for unary ones. – Stefan Jun 10 '14 at 09:52

3 Answers3

13

Use CRTP:

template<class Derived>
struct A {
    Derived& f() {return static_cast<Derived&>(*this);}
};

struct B : A<B> {
    int x;
    B(int x) : x(x) {}
};
Community
  • 1
  • 1
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • Yes, this works and looks like an acceptable solution. Thanks! So this is a well-known pattern, then. – Stefan Jun 10 '14 at 10:01
  • One question: Maybe it doesn't matter, but wouldn't it be better to cast the pointer before dereferencing: `return *static_cast(this)`? – Stefan Jun 10 '14 at 10:06
  • @Stefan it's entirely a matter of taste; `this` is never null so it really doesn't make a difference, and any base pointer adjustment happens exactly the same way in both cases. – ecatmur Jun 10 '14 at 10:50
  • Unfortunately, this turns quite ugly with multiple levels of inheritance, since the template arguments must be passed through all the way to the base class! So we need `template struct B : A` if there should be a `struct C : B`, and there is no way to used recursive defaults: `template struct B : A`. But I didn't mention that I have multiple layers of inheritance... – Stefan Jun 10 '14 at 10:58
  • @Stefan I don't really see the ugliness. Yes, you have for each level of inheritance, to do like `struct B: A {}; struct C: B, A { using A::operator*; } };`... but this begs the question: does your iterator hierarchy **need** to be multilevel-deep? (or maybe you have another mixin, besides `A` that you can multi-inherit in your iterators?) – Massa Jun 10 '14 at 13:10
  • @Massa: Interesting idea to use multiple inheritance, although I have to specify `using A::f;` every time. If classes have real names instead of `A`, `B`, `C`, all this becomes quite verbose, more than copy-pasting `operator++()` into every derived class. Of course, the multi-level inheritance design is up for discussion if there is no easy solution to put things like `operator++()` in the base class and be done with it. Using no inheritance at all and instead copy-pasting a few methods is entirely an option... – Stefan Jun 10 '14 at 13:41
  • @Stefan: you only need to specify `using A::f` if you go multi-level; you can always use things like `template struct increment_pointer_t { Cl& operator++() { ++internal_pointer; return static_cast(*this); } }; template struct do_other_thing_t { Cl& other_thing() { return static_cast *this; } }; class one_and_other : public some_other_base, public increment_pointer_t, public do_other_thing_t {};` and `one_and_other` does not need `using`: there is no ambiguity as long as `other_base` does not define `operator++` or `other_thing`. – Massa Jun 10 '14 at 13:48
  • (each of the CRTP base templates is a [mixin](http://en.wikipedia.org/wiki/Mixin) -- also [here](http://stackoverflow.com/questions/tagged/mixins%20c%2b%2b)) – Massa Jun 10 '14 at 13:49
0

F can return A so you can't store a struct A in a struct B. But you can do the opposite with pointers.

    struct A {
  A& f() {return *this;}

  template <typename T>
  T& g() {return *(T*)this;}

  template <typename T>
  T& h(T& unused) {return *((T *)this);}
};

struct B : A {
  int x;
  B(int x) : x(x) {}
  B(const B & other) : x(other.x) {}
};

int main() {

  A  *b = new B(12);

  A *b1 = new B( (const B&)b->f());                                                                                 
  B b2 = b->g<B>();                                                                                                                                                                                 
  A b3 = B->h(*b);                                                                                                                                                             
}
Guillaume Kiz
  • 443
  • 4
  • 10
0

@ecatmur's answer solves the problem as specified, but unfortunately CRTP won't go much further than this. Consider the case where we have 2 levels of inheritance:

template <typename D>
struct A {
    D& f() {return *static_cast<D*>(this);}
};

template <typename D>
struct B : A<D> {
    int x;
    B(int x=0) : x(x) {}
};

struct C : B<C> {
    double y;
    C(double y=0.) : y(y) {}
};

int main() {
   C c(12.5);
   C c1 = c.f();   // works
}

This works fine if only C needs to be instantiated, but it appears to be completely impossible now to instantiate B:

int main() {
   B<B> b;   // error: type/value mismatch at argument 1 in template parameter list
}

I tried a bunch of variations, including with typename and template keywords but didn't succeed to get an instance of B. Neither is it possible to use a recursive default template argument:

template <typename D=B> struct B : A<D> {};    // error

So CRTP appears to be only a good idea in single inheritance situations.

I ended up with the simple solution to just return a reference to the base class A&. Since there is no loss of information this way, the derived classes can always cast the returned reference to the derived type:

struct A {
    A& f() {return *this;}
};

struct B : A {
    int x;
    B(int x=0) : x(x) {}
};

struct C : B {
    double y;
    C(double y=0.) : y(y) {}
};

int main() {
    B b(12);
    B b1 = (B&)b.f();  // works

    C c(12.5);
    C c1 = (C&)c.f();  // works
}

So, it appears the right approach is to just return a base class reference and cast as necessary in the derived classes.

Stefan
  • 4,380
  • 2
  • 30
  • 33