Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ member-function chaining return types and derived classes

Given this contrived example:

struct point_2d {
  point_2d& x( int n ) {
    x_ = n;
    return *this;
  }

  point_2d& y( int n ) {
    y_ = n;
    return *this;
  }

  int x_, y_;
};

struct point_3d : point_2d {
  point_3d& z( int n ) {
    z_ = n;
    return *this;
  }

  int z_;
};

int main() {
  point_3d p;
  p.x(0).y(0).z(0); // error: "point_2d" has no member named "z"
  return 0;
}

the idea is to use "member-function chaining" to be able to call more than one member-function in a row. (There are many examples of this; the above is the shortest one I could think of for the purpose of asking this question. My actual problem is similar and is described below.)

The problem is that if a derived class adds its own chaining member-functions but you call a base class's member function first, you get a base-class reference that of course won't work for calling a derived class's member-function.

Are there any clever ways to solve this problem and still maintain the ability to do member-function chaining?


The Actual Problem

My actual problem is that my base class is an exception and my derived class is a class derived from the base exception. For those classes also, I want to use member-function chaining:

class base_exception : public std::exception {
  // ...
  base_exception& set_something( int some_param ) {
    // ...
    return *this;
  }
};

class derived_exception : public base_exception {
  // ...
};

int main() {
  try {
    // ...
    if ( disaster )
      throw derived_exception( required_arg1, required_arg2 )
            .set_something( optional_param );
  }
  catch ( derived_exception const &e ) {
    // terminate called after throwing an instance of 'base_exception'
  }
}

The problem is that set_something() returns base_exception but the catch expects a derived_exception. Of course a human can tell that the actual type of the exception is a derived_exception but the compiler apparently can't tell.

That's the problem I'm really trying to solve, i.e., how to have a base exception class be able to set optional parameters on the exception object yet return an instance of the derived type. The point_2d example I gave above is (I believe) a smaller and simpler version of the same problem for people to understand and that a solution to the smaller problem will also solve my actual problem.

Note that I did consider making base_exception a template and pass in the derived type like:

template<class Derived>
class base_exception {
  // ...
  Derived& set_something( int some_param ) {
    // ...
    return *this;
  }
};

I believe that in fact does solve the problem, but it's not a perfect solution because if another class more_derived_exception derives from derived_exception, then we're back to the same problem.

like image 738
Paul J. Lucas Avatar asked Feb 16 '11 17:02

Paul J. Lucas


2 Answers

What you're looking for is the Named Parameter Idiom, which I am copying from this StackOverflow answer. Rather than return a reference to the actual object, you return a reference to a special parameter object, and rely on a constructor for your exception object to do an implicit conversion once all the parameters are filled in. It's quite clever, really.

like image 125
Mark Ransom Avatar answered Nov 06 '22 03:11

Mark Ransom


Hi there I just had a similar Problem and here My Solution:

template<class DerivedOptions>
class SomeOptions
{
  private:
    DerivedOptions* derived;
    int param1_;
  public:
    SomeOptions()
    {
        derived = reinterpret_cast<DerivedOptions*>(this);
    }

    DerivedOptions & set_some_options(int param1)
    {
        param1_ = param1;
        return *derived;
    }
};

struct MoreOptions: public SomeOptions<MoreOptions>
{
  private:
    int more_;
  public:
    MoreOptions & set_more_options(int more)
    {
        more_ = more;
        return *this;
    }
};

Defininately contains some I know what I'm doing foo but on the other hand (at least in my application) the Base class is not meant to be used without inheritence.

Best regards, Regi

like image 30
regithestar Avatar answered Nov 06 '22 02:11

regithestar