Re: C# Properties, inner classes and CRTP
Thank you for reading everything!
Vidar Hasfjord wrote:
stroumpf2004-gtalk@yahoo.de wrote:
A primary goal of inheritance is to avoid code redundancy (that causes
more trouble than just work, btw).
Ok, if I understand correctly fragmental classes are there to *inject*
code, not to establish OOP type relationships (typical "is-a"
relationships established by inheritance). This leads me to think that
the use of inheritance is the wrong mechanism then.
It seems like "code injection" is a good description of what I'm trying
to do. But this doesn't mean that there is no "is-a" relationship. As I
said, the mechanism does almost the same as CRTP, so most arguments
against my proposal would also bury CRTP.
CRTP, like virtual functions, allow a base class to call methods of the
deriving class. The difference is, with CRTP the deriving class is
known at compile time, which brings a performance benefit. However, as
the base class is a template, there is no way to create a common handle
(pointer) to all classes using that template.
I don't need to show why CRTP would be useful - it's enough to have a
look at common practice and documentation.
Let's have a look at a little CRTP example. Of course this simple
problem can be solved in a different way, but it's nice for analysis.
------- with virtual function ---------------------
template<typename Derived> struct CalculatorEngine {
void run() {
char* input = ui_v();
... // do something with input
}
private:
... // member variables and methods
virtual char* ui_v()=0;
};
struct CommandlineCalculator : CalculatorEngine {
virtual char* ui_v() {
char* res;
cout << "What should I calculate?";
cin >> res;
return res;
}
};
struct PopupCalculator : CalculatorEngine {
virtual char* ui_v() {
// assuming query_box is a function in my ui toolkit..
return query_box("What should I calculate?");
}
};
-> easy to read
-> CalculatorEngine* can be used as a common handle for both calculator
types
-> overhead caused by vtable lookup
------- with CRTP -----------------
template<typename Derived> struct CalculatorEngine {
void run() {
char* input = ui_redirect();
... // do something with input
}
private:
... // member variables and methods
char* ui_redirect() {
return static_cast<Derived*>(this)->ui_imp();
}
};
struct CommandlineCalculator : CalculatorEngine<CommandlineCalculator>
{
char* ui_imp() {
char* res;
cin >> res;
return res;
}
};
struct PopupCalculator : CalculatorEngine<CommandlineCalculator> {
char* ui_imp() {
// assuming query_box is a function in my ui toolkit..
return query_box("What should I calculate?");
}
};
-> efficient
-> nasty workaround
-> can't create a common handle for both calculator types (but that's
not intended anyway)
------- with fragmental class -----------------
fragmental struct CalculatorEngine {
void run() {
char* input = ui_redirect();
... // do something with input
}
private:
... // member variables and methods
fragmental ui_frag()=0;
};
struct CommandlineCalculator : CalculatorEngine<CommandlineCalculator>
{
char* ui_frag() {
char* res;
cin >> res;
return res;
}
};
-> easy to read and understand, like the solution with virtual
functions
-> efficient like the CRTP solution (machine code will be almost
equivalent)
-> can't create a common handle for both calculator types (but that's
not intended anyway)
---------------------------
It seems the problem can be solved by the concept proposal for C++0x:
template <typename T>
concept Property <P> {
typedef T value_type;
// All P must implement:
const T& P::get () const;
void P::set (const T&);
// Default operations (these are *injected*):
P& P::operator = (const T& v) {set (v); return *this;}
const T& P::operator T () {return get ();}
}
Usage:
class C {
float angle_;
public:
struct Angle {
void set (float f) {...}
float get() {...}
} angle;
concept_map <float> Property <Angle>;
};
This is a more regular method of injecting code (or formally "add an
interface" to a class). Having a third way --- fragmental classes ---
seems unnecessary.
Well, my solution seems more natural to me.. but for now I agree it's
not absolutely necessary - I'll think more about that.
However, as I understand it, concepts can't add data elements to a
class - right? So how would you implement a general CRTP example with
concept_map?
-------
I'd like to talk about fragmental classes from yet another point of
view. Consider the above example with virtual functions. Imagine the
compiler gets two additional pieces of information:
- CalculatorEngine*, CalculatorEngine& and CalculatorEngine will never
be used as types (except as a base class). We can call that the
not_a_type characteristic.
- Subclasses of CommandlineCalculator will never override the
implementation of ui_v(). We can call this a "final virtual" method.
With this extra information, the compiler could generate a machine code
for CommandlineCalculator that does not need any vtables - simply by
giving CommandlineCalculator its own version of run(), with a
non-virtual call to CommandlineCalculator::ui_v().
That's exactly what I try to do with the fragmental class. The
fragmental keyword on the class tells the compiler that the class will
never be used to create pointers, references or instances. Now we would
need a 'final' keyword for the virtual function implementation in
CommandlineCalculator, to express the second piece of information. My
proposal works a little bit different, syntactically - the former
'virtual' method is now called 'fragmental', to describe a different
way of redirecting function calls.
template<
typename Derived
not_a_type struct CalculatorEngine {
void run() {
char* input = ui_v();
... // do something with input
}
private:
... // member variables and methods
virtual char* ui_v()=0;
};
struct CommandlineCalculator : CalculatorEngine {
final virtual char* ui_v() {
char* res;
cout << "What should I calculate?";
cin >> res;
return res;
}
};
-> could be translated into machine code without any vtables!
-> would be as efficient as CRTP
----------------------------------------------------------------------------
----------------
Also, why the need for enclosing_object and offsetof tricks? It seems
to me that, in the common case at least, you can just as well just
store the implementation data in the property itself:
class C {
public:
struct Angle {
void set (float f) {...}
float get() {...}
private: float value;
friend C; // Give access to enclosing class (optional).
} angle;
concept_map <float> Property <Angle>;
};
Now implement the enclosing class in terms of (angle.value) if access
is needed, or plain (angle) to go through the Property interface as
outside clients must do.
Now it's a question of finding a good example. At home I frequently
wish to have direct access to the enclosing object - and people in
forums frequently ask for the same.
The following code provides two properties that control the same value
- degree angle and radian angle. Similar tricks are imaginable for
celsius and Kelvin, or for cartesian and barycentric / other
coordinates.
I will use my syntax, I think it is obvious how that translates into
classic C++. (i don't care about const or not const here..)
class C {
const static pi = ...; // I think we can get the pi value from
somewhere..
float angle_deg_; // angle from 0 to 360
public:
// for programs that operate with degree angles
Float angle_deg {
void set(float f) {
while(f>=360) f-=360;
while(f<0) f+=360;
enclosing_object->angle_deg_=f;
}
float get() {
return enclosing_object->angle_deg_;
}
};
// for programs that operate with radian angles
Float angle_rad {
void set(float f) {
enclosing_object->angle_deg=f*180/pi;
}
float get() {
return enclosing_object->angle_deg*pi/180;
}
};
};
Another example is a state machine that makes heavy use of data in the
enclosing class.
class StateMachine {
typedef char Command;
vector<..> vec;
struct State {
virtual void step(Command c)=0;
} *current_state;
State state_0 {
virtual void step(Command c) {
switch(c) {
case 'a':
vec.push(..);
current_state=state_1;
...
}
}
}
State state_1 {
...
}
};
I think it's a hole in the language to hide an obvious information from
the programmer, and instead make him depend on nasty macros or
technically redundant references.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]