9

I'd like to build a base (abstract) class (let's call it type::base) with some common funcionality and a fluent interface, the problem I'm facing is the return type of all those methods

  class base {
    public:
       base();
       virtual ~base();

       base& with_foo();
       base& with_bar();
    protected:
       // whatever...
  };

Now I could make subtypes, e.g.:

  class my_type : public base {
    public:
      myType();        
      // more methods...
  };

The problem comes when using those subtypes like this:

 my_type build_my_type()
 {
    return my_type().with_foo().with_bar();
 }

This won't compile because we're returning base instead of my_type.

I know that I could just:

 my_type build_my_type()
 {
    my_type ret;
    ret.with_foo().with_bar();

    return ret;
 }

But I was thinking how can I implement it, and I've not found any valid ideas, some suggestion?

blaxter
  • 335
  • 3
  • 10
  • I've removed the namespace stuff because it has nothing (as far as I can see) to do with the question –  Jun 24 '09 at 22:48

5 Answers5

4

This problem of "losing the type" can be solved with templates - but it's rather complicated.

Eg.

class Pizza
{
  string topping;
public:
  virtual double price() const;
};

template <class T, class Base>
class FluentPizza : public Base
{
  T* withAnchovies() { ... some implementation ... };
};

class RectPizza : public FluentPizza<RectPizza, Pizza>
{
  double price() const { return length*width; :) }
};

class SquarePizza : public FluentPizza<SquarePizza, RectPizza>
{
   ... something else ...
};

You can then write

SquarePizza* p=(new SquarePizza)->withAnchovies();

The pattern is that instead of

class T : public B

you write

class T : public Fluent<T, B>

Another approach could be not to use fluent interface on the objects, but on pointers instead:

class Pizza { ... };
class RectPizza { ... };
class SquarePizza { ... whatever you might imagine ... };

template <class T>
class FluentPizzaPtr
{
  T* pizza;
public:
  FluentPizzaPtr withAnchovies() {
    pizza->addAnchovies(); // a nonfluent method
    return *this;
  }
};

Use like this:

FluentPizzaPtr<SquarePizza> squarePizzaFactory() { ... }

FluentPizzaPtr<SquarePizza> myPizza=squarePizzaFactory().withAnchovies();
jpalecek
  • 47,058
  • 7
  • 102
  • 144
  • Could you provide an example? It could be interesting. – liori Jun 24 '09 at 23:07
  • cf. the edit. It may be interesting, I don't know if I'd personally use it, though... – jpalecek Jun 24 '09 at 23:12
  • So you actually switch to pointers. Then you can just use simple polymorphism, without CRTP, and return base*. – liori Jun 24 '09 at 23:18
  • You can imagine references (if you don't want pointers) there. I just wouldn't make the methods always return a copy, but that's not the core issue. The main point of CRTP is to remove the decay to base (or base*, or base&), which is what I thought you'd like to avoid. – jpalecek Jun 24 '09 at 23:29
  • Now, the problem I see is that if in the fluent interface you return `return *this;` you are returning (.e.g.) `FluentPizza` instead of `RectPizza` – blaxter Jun 25 '09 at 07:50
  • Also, a static_cast( *this ) won't work because there is no constructor RectPizza for FluentInterface – blaxter Jun 25 '09 at 08:17
  • 1
    If you're returning pointers or references, return a static_cast(this) or static_cast(*this) (the latter would work for copies too, if you don't mind changing the original too. If you want to return a copy and leave the original intact, do something like "T ret=*(T*)this; do_something_with_ret; return ret;" – jpalecek Jun 25 '09 at 11:36
4

You should be returning references/pointers, and you should not need to keep the type information.

class base {
  public:
     base();
     virtual ~base();

     base &with_foo();
     base &with_bar();
  protected:
     // whatever...
};

class my_type : public base {
  public:
    my_type();        
    // more methods...
};

base *build_my_type()
{
   return &new my_type()->with_foo().with_bar();
}

You already have a virtual destructor. Presumably you have other virtual functions. Access everything through the base type and the virtual functions declared there.

dave4420
  • 46,404
  • 6
  • 118
  • 152
0

One solution would work like this:

return *static_cast<my_type*>(&my_type().with_foo().with_bar());

Using static_cast basically tells the compiler 'I know what I'm doing here'.

Tobias
  • 6,388
  • 4
  • 39
  • 64
-1

In C++ you should be returing pointers or references rather than values. Also, you might want to explain what you mean by "fluent interfaces".

  • 1
    Sorry - as soon as I see the name "Martin" my bullsh*t detector goes off at full volume and I can read no more. Doesn't seem to matter if it's a forename or a surname, either. –  Jun 24 '09 at 22:56
-2

The way I'd do it in C#, and I believe it would work in C++ too is to provide a default implementation for with_foo() and with_bar()... Forgive my c#, but:

class base {
  virtual base with_foo()
  { throw new NotImplementedException(); }
  virtual base with_bar();
  { throw new NotImplementedException(); }
}
Matthew Scharley
  • 127,823
  • 52
  • 194
  • 222
  • But this doesn't fix the problem - the problem is a type mismatch, which is irrelevant of the question. – jpalecek Jun 24 '09 at 22:49