Topic: Covariant returns, inheritance, and named parameter idiom?!
Author: Dave Shawley <daveshawley@gmail.com>
Date: Mon, 08 May 2006 14:45:09 -0500 Raw View
First of all, sorry for the cross post but I am trying to elicit
responses from the standardistas as well as the mere mortals (like
myself) ;)
Well if the subject got your attention, then you might be able to make
some sense out of what I am trying to do here and explain to me why it
doesn't work. In brief, I'm working through some options for
implementing the named parameter idiom. What I would like to do is to
have classes include an inner class named "options" and accept an
options instenad as the only argument to a constructor. The options
class would implement setter functions that simply set a member and
return a reference to ``this''. Pretty simple (right?) and a common
implementation to do chaining. The difficulty comes when we start doing
inheritance. I planned on implementing this using something like the
following:
struct Base {
struct options {
virtual ~options() {}
virtual options& setBaseOption (int x) {
m_x = x;
return *this; }
int m_x;
};
Base (options& opts) : m_baseOptions(opts) {}
virtual ~Base() {}
options m_baseOptions;
};
struct Derived : public Base {
struct options : Base::options {
virtual ~options() {}
virtual options& setDerivedOption (int y) {
m_y = y;
return *this; }
int m_y;
};
Derived (options& opts) : Base(opts), m_derivedOptions(opts) {}
virtual ~Derived() {}
options m_derivedOptions;
};
int
main()
{
Derived object(Derived::options()
.setBaseOption(1)
.setDerivedOption(2));
return 0;
}
Now this doesn't work with g++ 4.0.1:
covariant.cpp: In function 'int main()':
covariant.cpp:32: error: 'struct Base::options' has no member named
'setDerivedOption'
I think that it should! I did a bunch of other variations using
pointers instead of references and stuff like that without any better
results. Is this a case of g++ not implementing covariant returns
properly? I've showed that at runtime this would work - inside of
setBaseOption, typeid(this) does return the expected type (Derived).
However, it looks like the compiler is statically typing the return
value of setBaseOption to Base&. Is this correct?
Do I have to explicitly override setBaseOption inside of
Derived::options to make this work? This does the trick for g++, but is
it required by the ANSI/ISO standard? For example, adding the following
member to Derived::options makes things work, but I really don't want
to do this.
virtual options& setBaseOption (int x) {
Base::options::setBaseOption(x);
return *this;
}
It is going to make maintenance of the class hierarchy a nightmare
since it requires you to modify every derived class whenever you modify
a base class :(
Has anyone out there tried anything like this? I'm really trying to get
the named parameters idiom to work for a class hierarchy that allows
for sub classes to pass base class parameters back up to the hierarchy
seemlessly. Is there a better approach out there?
Thanks in advance,
Dave.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: David Abrahams <dave@boost-consulting.com>
Date: Tue, 09 May 2006 09:18:44 -0500 Raw View
Dave Shawley <daveshawley@gmail.com> writes:
> In brief, I'm working through some options for implementing the
> named parameter idiom.
Please consider using the Boost Parameter library:
http://www.boost.org/libs/parameter
It doesn't exhibit the deadly coupling effects of the chaining idiom.
Also, in the upcoming Boost release it will be much easier to use, and
we have implemented new features such as unnamed function parameter
support (for parameters that can be distinguished by their type
alone), and named and unnamed template parameter support.
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: "Alf P. Steinbach" <alfps@start.no>
Date: Tue, 09 May 2006 09:18:44 -0500 Raw View
* Dave Shawley:
> Has anyone out there tried anything like this? I'm really trying to get
> the named parameters idiom to work for a class hierarchy that allows
> for sub classes to pass base class parameters back up to the hierarchy
> seemlessly. Is there a better approach out there?
Yes.
First, the design problem. If you insist on letting Derived::Options
being derived from Base::Options, you'll increase the number of
available options at every level of class derivation. Since class
derivation is about simplifying things, not adding to complexity ad
infinitum, I don't think that's what you really want, on reflection.
Recognizing that the case you have encountered is a special one,
treating it is a seldom encountered special case and defining a y-value
setter in Derived::Options is IMHO the best solution. You don't need
the member functions virtual, btw. You don't need actual covariance.
Second, the technical problem.
I really don't recommend this, because it's horrible at the design
level, but it "works" at the technical level (note the 'const's):
class Base
{
private:
int myX;
public:
struct OptionValues { int xVal; OptionValues(): xVal( 0 ) {} };
template<class C> struct Options_: C::OptionValues
{
typedef typename C::template Options_<C> ResultType;
ResultType& derivedSelf() { return static_cast<ResultType&>(
*this ); }
ResultType& x( int anXVal ){ this->xVal = anXVal; return
derivedSelf(); }
};
typedef Options_<Base> Options;
Base( OptionValues const& options ): myX( options.xVal ) {}
void foo() const { std::cout << myX << "\n"; }
};
class Derived: public Base
{
private:
int myY;
public:
struct OptionValues: Base::OptionValues
{ int yVal; OptionValues(): yVal( 0 ) {} };
template<class C> struct Options_: Base::Options_<C>
{
typedef typename C::template Options_<C> ResultType;
ResultType& derivedSelf() { return static_cast<ResultType&>(
*this ); }
ResultType& y( int aYVal ){ this->yVal = aYVal; return
derivedSelf(); }
};
typedef Options_<Derived> Options;
Derived( OptionValues const& options )
: Base( options ), myY( options.yVal )
{}
void foo() const { Base::foo(); std::cout << myY << "\n"; }
};
int main()
{
Derived d( Derived::Options().x( 1 ).y( 2 ) );
d.foo();
}
Interestingly, g++ 3.4.4 is happy with a 'using' declaration for the
second 'derivedSelf', whereas Visual C++ 7.1 says "no such thing".
--
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?
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: AlbertoBarbati@libero.it (Alberto Ganesh Barbati)
Date: Tue, 9 May 2006 16:11:16 GMT Raw View
Dave Shawley ha scritto:
>
> struct Base {
> struct options {
> virtual ~options() {}
> virtual options& setBaseOption (int x) {
> m_x = x;
> return *this; }
> int m_x;
> };
> Base (options& opts) : m_baseOptions(opts) {}
> virtual ~Base() {}
> options m_baseOptions;
> };
>
> struct Derived : public Base {
> struct options : Base::options {
> virtual ~options() {}
> virtual options& setDerivedOption (int y) {
> m_y = y;
> return *this; }
> int m_y;
> };
> Derived (options& opts) : Base(opts), m_derivedOptions(opts) {}
> virtual ~Derived() {}
> options m_derivedOptions;
> };
>
> int
> main()
> {
> Derived object(Derived::options()
> .setBaseOption(1)
> .setDerivedOption(2));
> return 0;
> }
>
> Now this doesn't work with g++ 4.0.1:
>
> covariant.cpp: In function 'int main()':
> covariant.cpp:32: error: 'struct Base::options' has no member named
> 'setDerivedOption'
>
> I think that it should! I did a bunch of other variations using
> pointers instead of references and stuff like that without any better
> results. Is this a case of g++ not implementing covariant returns
> properly? I've showed that at runtime this would work - inside of
> setBaseOption, typeid(this) does return the expected type (Derived).
> However, it looks like the compiler is statically typing the return
> value of setBaseOption to Base&. Is this correct?
Yes, it's correct: it shouldn't work.
> Do I have to explicitly override setBaseOption inside of
> Derived::options to make this work?
Yes.
> This does the trick for g++, but is
> it required by the ANSI/ISO standard? For example, adding the following
> member to Derived::options makes things work, but I really don't want
> to do this.
>
> virtual options& setBaseOption (int x) {
> Base::options::setBaseOption(x);
> return *this;
> }
>
> It is going to make maintenance of the class hierarchy a nightmare
> since it requires you to modify every derived class whenever you modify
> a base class :(
Covariance was not meant for this. You are just blaming a tool because
it doesn't do something that it wasn't designed for but you desperately
need.
There's a very good reason for not having covariance work as you would
like to, namely that not all virtual functions return *this.
In fact I have seen some time ago a proposal to allow a new kind of
member functions that would always return *this using the static type of
the argument. For example:
struct Base
{
this Method() // proposed syntax, not C++
{
// body here, "return value;" not allowed, "return;" allowed
}
};
struct Derived : Base
{
};
void test()
{
Base b;
Base& rb = b.Method();
assert(&rb == &b);
Derived d;
Derived& rd = d.Method();
assert(&rd == &d);
}
Notice that there are *no* virtual functions involved. It's all just
syntactic sugar. Method() is declared *and* implemented exactly as a
non-virtual function returning void, but the expression
o.Method()
is evaluated as
(o.Method(), o)
at the call site. I haven't heard of this proposal recently, does anyone
know if it has been formalized in some way? Anyway, although I like this
syntax very much, I acknowledge that the upcoming revision C++ has much
more important issues to consider.
> Has anyone out there tried anything like this? I'm really trying to get
> the named parameters idiom to work for a class hierarchy that allows
> for sub classes to pass base class parameters back up to the hierarchy
> seemlessly. Is there a better approach out there?
What about the Boost Parameter library <http://boost.org/libs/parameter/> ?
HTH,
Ganesh
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]