|
I'm sorry, I don't quite get what you mean. I did try to do it with a member function, as I said in my previous posting (I wrote "I moved the use of OVERRIDE to inside the class definition and there I declared an inline function to do the tests"). And my previous posting also explains where the problems are with this approach: I wrote "However, it seems that although GCC accepts &A::foo from B if A::foo is protected, Comeau does not.", which means that the trick doesn't work on all compilers. Maybe I should write more clearly? BTW, even if it did work, your trick wouldn't work as-is, because it would lead to duplicate declarations of the same function (when using OVERRIDE multiple times because of function overloads). When I tried it out I solved that by overloading the test function with the same parameter list as the overriding function. Please work with me to find a solution that works on all compilers, by finding a solution to the unique-naming issue that I brought up in my previous post. Thank you.
-- Bart
|
|
|
|
|
Eureka, I found it:
#define OVERRIDE(baseclass,returntype,derivedclass,func,params) \
static void func params \
{ \
struct returntype##derivedclass##_##func##_override_test_class : public baseclass \
{\
returntype (baseclass::*fntestptr) params;\
baseclass *basetestptr;\
void derivedclass##_##func##_override_test_class_function()\
{\
fntestptr = &baseclass::func;\
basetestptr = reinterpret_cast<derivedclass*>(0);\
}\
};\
}\
returntype derivedclass::func params
|
|
|
|
|
OK, let's take a look at this.
First of all, the construct you use at the end (basically declaring something like void A::foo() ) is not standard C++.
Second, I'd give the checking function a different name than "func", otherwise they're going to call the checking func instead of the real one.
Third, this doesn't fix the problem I was describing -- some compilers (Comeau, specifically) don't allow you to take the address of baseclass::func when it's protected, even when you're inside a derived class. I compiled the following code (slightly adapted from your code so that it's standard C++):
#define OVERRIDE(baseclass,returntype,derivedclass,func,params) \
static void func##_test_func params \
{ \
struct returntype##derivedclass##_##func##_override_test_class : public baseclass \
{\
returntype (baseclass::*fntestptr) params;\
baseclass *basetestptr;\
void derivedclass##_##func##_override_test_class_function()\
{\
fntestptr = &baseclass::func;\
basetestptr = reinterpret_cast<derivedclass*>(0);\
}\
};\
}\
returntype func params
class A
{
protected:
void foo();
};
class B : public A
{
public:
OVERRIDE(A,void,B,foo,());
};
and Comeau told me:
"ComeauTest.c", line 26: error: protected function "A::foo" is not accessible
through a "A" pointer or object
OVERRIDE(A,void,foo,());
So, there you go. Try and fix that. And get it through Comeau !
-- Bart
|
|
|
|
|
First of all, who cares about Comeau !!!
Ask them why can't I get a pointer to a protected function, its ilogical.
Second, don't use my OVERRIDE in the definition file (.h file) just use it as your original code has: in the declaration file (.cpp file) so no problems in "standard C++" and also no name ambiguity with the static function name.
|
|
|
|
|
So, OK, the thing is in the cpp file, I misinterpreted things because I moved to trying solutions in the header file in the mean time. Sorry!
Anyway, I care about Comeau, as I care about writing portable code. The compiler is closer to the next version of the standard than any other compiler I know, so I like to check my code with it for portability, so that it may still work in five years.
The fact that your version of OVERRIDE can be written like this shows even more clearly that taking the address of a protected member shouldn't be allowed. If this were allowed, I'd always be able to write a standard "protection circumvention mechanism" as follows:
class A
{
protected:
void foo();
};
void bar(A a)
{
}
void baz(A a)
{
struct ProtectionCircumventer : public A
{
static void get_foo(void (A::*a_foo_ptr)())
{
a_foo_ptr = &A::foo;
}
};
void (A::*foo)();
ProtectionCircumventer::get_foo(foo);
(a.*foo)();
}
This means that, with a bit of work, I could circumvent all the protection of A or any other class! Okay, you might say, write a derived class and call it from there, that's the same thing, right? And then I would say: no, because in that case the object has to be of your derived type in order for you to be able to call A 's members. So, circumvention is not possible if you forbid taking the address of protected members, and it is possible if you allow it. Unlogical to forbid it? I don't think so.
-- Bart
|
|
|
|
|
Hey folks, I can see I've really started a debate here. I'm sorry that I'm not making any useful contributions, but I'm living quite happily with my if(0) and my non-fundamentalist compiler, so I guess I'll leave the discussion in your capable hands.
|
|
|
|
|
Your macro is defined as below:
#define OVERRIDE(baseclass,returntype,derivedclass,func,params) \
struct derivedclass##_##func##_override_test_class \
{ \
returntype (baseclass::*fntestptr) params; \
baseclass *basetestptr; \
derivedclass##_##func##_override_test_class() \
{ \
fntestptr = &baseclass::func; \
basetestptr = reinterpret_cast<derivedclass*>(0); \
} \
}; \
returntype derivedclass::func params
But don't you think the statment:
basetestptr = reinterpret_cast*ltderivedclass*>(0); \
is unnecessary? Deleting it seems to make no difference from the original effect of this OVERRIDE macro.
Furthermore, your OVERRIDE macro can be further simplied:
#define OVERRIDE(baseclass,returntype,derivedclass,func,params) \
void function_derivedclass##_##func##_override_test() \
{ \
returntype (baseclass::*fntestptr) params = &baseclass::func; \
} \
returntype derivedclass::func params
I stripped everything except a core assignment-statement in the function to do the function overriding checking. Do you think I am right?
|
|
|
|
|
Okay, let's take this one at a time. About your first comment, that the statement
basetestptr = reinterpret_cast<derivedclass*>(0);
is redundant: it isn't, otherwise I wouldn't have put it there. If you write
class A
{
void foo() {}
};
class B
{
void foo();
};
OVERRIDE(A,void,B,foo,())
{
}
it would compile without this statement, as the only thing the rest of the macro does is to take the address of A::foo . The statement is intented to check whether B is actually a derived class of A , so that it can make sure that B::foo is actually an override of A::foo instead of that it just has the same signature (as the other part of the checking macro does).
About the second part of your comment, and the stripped version you present: yes, this is definitely simplified, but unfortunately it doesn't serve it's purpose as well. I tried to do it this way originally, but I changed it because I found that this simplicity has a disadvantage. I'll tell you what the disadvantage is: fntestptr is not used in the function, and at the highest warning level most compilers will give you a warning on this! The last thing you want is an unfixable warning for every time you use this macro, so I experimented with various compilers to see what would give me the least amount of unnecessary warnings (zero, preferably ). It turned out that the only way that I could fool both GCC and Comeau's compiler into not emitting a warning was to define a class with the test variables as its public members, and to make the class extern. If the variables were included as locals in the function GCC and Comeau would emit a warning, and if I made the class "static" (i.e., not exported) Comeau would still emit a warning about a never-instantiated non-exported class or something.
So, although you are correct in stating that the macro can be simplified code-wise, the effects of this simplification are not desirable.
-- Bart
|
|
|
|
|
Now, I agree that adding
basetestptr = reinterpret_cast<derivedclass*>(0);
is better than not adding it, which does more checking and make your code more secure.
But for the issue of "some variable not used in a function" warning from the compiler, I can add
(void)fntestptr;
just following the assign-statement of fntestptr, which make `gcc -Wall' quiet again.
And further more, I realized later that the macro can be further simplified.
#define OVERRIDE(baseclass,returntype,derivedclass,func,params) \
returntype (baseclass::*baseclass##_fntestptr) params = &baseclass::func; \
baseclass *baseclass##_basetestptr = reinterpret_cast<derivedclass*>(0); \
returntype derivedclass::func params
This time, no "struct", no function, but only two global variables.
However, I'm not persuading you into taking my may of the OVERRIDE macro, everyone has his preference, and the macro is just a facility of carrying out code checking during coding, isn't it?
|
|
|
|
|
I guess that should work, and it is pretty simple indeed. Unfortunately, an issue has come up (overriding protected member functions) that precludes this solution. Also, I found that (void)fntestptr works for VC++ but not for Comeau, and I'd like it to at least compile without warnings in all of the compilers I have available. Indeed it's just a checking facility, so I don't care how simple or complicated it is, as long as it works as advertised.
-- Bart
|
|
|
|
|
Here is another way to force derived classes to implement function(s) from the base class. This may not be the way to go in all situations.
It requires the base class's function to not be implented.
class BaseClass
{
public:
virtual void foo(int n = 3) = 0; //This makes the function a pure virtual
};
class A : public BaseClass //this class is fine, because it implements foo
{
public:
virtual void foo(int n = 3)
{
std::cout << "A::foo(" << n << ")" << endl;
}
};
class B : public BaseClass //the compiler will stop, because B does not implement all of the base's pure virtual functions.
{
public:
virtual void foo();
};
int main()
{
BaseClass* baseClass = new A;
baseClass->foo(); // prints "A::foo".
}
|
|
|
|
|
Definitely a good solution when you've got the opportunity. This only works if your policy is to *only* implement functions in leaf classes. Unfortunately such a policy is not always useful. It can make some kinds of inheritance very impractical, especially when your derived class is intended to "adapt" the behaviour of a superclass (e.g., when you derive your own class from a CWnd-like class, override a callback and call the base class's implementation as part of your overridden behaviour). But still, if you have the chance to solve things like this, please do so. Macros aren't the nicest way to solve things.
BTW, there is a minor mistake in your post, as the code you've given does in fact compile. The compiler emits an error not on B 's class declaration, only when you try to instantiate B, e.g. when you write new B instead of new A . The error only occurs when you try to instantiate a class that lacks an implementation for one or more of its virtual member functions.
-- Bart
|
|
|
|
|
You're right. The solution is only useful in certain cases. I did forget about the compiler only complaining when a class is instanciating. Thanks for reminding me.
|
|
|
|
|
What about using explicit overriding of abstract functions ?
You then have both advantages:
- derivation from a class which implements functions
- compile time errors without macros
I modified the sample slightly.
class BaseClass
{
public:
virtual void foo(double nu) = 0;
};
class A : public BaseClass
{
public:
virtual void BaseClass::foo(int n) // Explicit override
{
std::cout << "A::foo(" << n << ")" << endl;
}
};
class B : public A
{
public:
virtual void BaseClass::foo(int n)
{
std::cout << "B::foo" << endl;
}
};
|
|
|
|
|
I would like this if it worked.
I tried to compile the following code sample using GCC:
class BaseClass
{
public:
virtual void foo(int nu) = 0;
};
class A : public BaseClass
{
public:
virtual void BaseClass::foo(int n)
{
std::cout << "A::foo(" << n << ")" << endl;
}
};
class B : public A
{
public:
virtual void BaseClass::foo(int n)
{
std::cout << "B::foo" << endl;
}
};
This would have to be the version that does compile, as the foo function signatures match exactly. However, when I compile it with GCC I get the following errors:
testme.cc:11: error: cannot declare member function `BaseClass::foo ' within `A '
testme.cc:20: error: cannot declare member function `BaseClass::foo ' within `B'
It might have been that GCC is wrong about the standard, so I tried it with Comeau's online compiler. Their compiler is extremely standards compliant (read the opinion of the Slashdot crowd in this article) and so far it has never failed me with regard to standards compliance. I always use this compiler to test if something doesn't compile because of a compiler quirk or because the code is simply incorrect.
Comeau gives me:
"ComeauTest.c", line 10: error: qualified name is not allowed
virtual void BaseClass::foo(int n) // Explicit override
^
"ComeauTest.c", line 19: error: qualified name is not allowed
virtual void BaseClass::foo(int n)
I also tried a similar trick using a "using" declaration. However, I can only write
class A : public BaseClass
{
public:
using BaseClass::foo;
virtual void foo(int n)
{
}
};
This makes sure that a function named "foo" exists in BaseClass (which is already pretty useful). However, it does not make sure that the signatures match. Does anyone know the correct syntax for something like this:
class A : public BaseClass
{
public:
using BaseClass::foo(int);
virtual void foo(int n)
{
}
};
Because if I had a choice, something like this would be the nicest solution! It doesn't compile on either GCC or Comeau, however. Apparently the standard doesn't allow "using" a specific overload of a function, or I'm missing some important syntax here.
Anyway, thanks for the idea! Maybe they'll allow this one day, and then I'll gladly do it this way.
-- Bart
|
|
|
|
|
My fault
Sorry, i don´t have access to the latest C++ standard.
It compiled fine under VC 7.1 and BCB X preview compiler (which i thought is based on the same frontend as Comeau - EDG ?) so i assumed explicit override is supported by the standard.
Anyway, i think too - they should add this to the standard.
-- Andre
|
|
|
|
|
I don't have access to the standard either -- however, Comeau works pretty well too. I too seem to remember that BCB is based on the EDG frontend, but I'm not sure how far back they branched and how many changes they made themselves. It's also very possible that both Comeau and GCC are wrong as per the current standard, especially Comeau, as it tends to support not the published standard but an approximation of the next version of the standard.
Now for some speculation. It might very well be possible that the code you wrote was valid in older incarnations of C++ but was removed recently (as in, late in the standardization process which was completed in 1998). MSVC++ in general seems to adhere to older incarnations of the standard; for instance, they only fixed the for-scope in their most recent compiler version while this change was already made in the C++ draft working paper of 1995.
-- Bart
|
|
|
|
|
FYI: BCB didn´t compile the explicit override
sample code. But the successor CBX and its
new preview compiler did.
By the way:
The intention for support of explicit override was to allow overriding a specific function implemented in multiple derived interfaces.
class Intf1
{
public:
virtual void foo1(int) = 0;
virtual void foo2(int) = 0;
};
class Intf2
{
public:
virtual void foo1(int) = 0;
};
class A : public Intf1, public Intf2
{
virtual void Intf1::foo1(int) { cout << "Intf1" << endl; }
virtual void Intf2::foo1(int) { cout << "Intf2" << endl; }
virtual void foo2(int) {}
};
int main(int argc, char* argv[])
{
A o;
Intf1& i1 = o;
Intf2& i2 = o;
i1.foo1(1); // prints Intf1
i2.foo1(1); // prints Intf2
}
Wether that´s good OOP programming/design or not i wonder how this can be done directly with a compiler not supporting explicit overrides.
(Only intermediate classes i can think of as a solution)
I think i should test more than only 2 compilers next time
|
|
|
|
|
To see if this was really the only way, I gave the "using" method a shot using GCC and Comeau, with the following code:
#include <iostream>
class BaseClass1
{
public:
virtual void foo(int nu) = 0;
};
class BaseClass2
{
public:
virtual void foo(int nu) = 0;
};
class A : public BaseClass1, public BaseClass2
{
public:
using BaseClass1::foo;
virtual void foo(int n)
{
std::cout << "Override of BaseClass1::foo." << std::endl;
}
using BaseClass2::foo;
virtual void foo(int n)
{
std::cout << "Override of BaseClass2::foo." << std::endl;
}
};
Unfortunately, this doesn't work. The using declarations don't instruct the compiler to see the first foo as an override of BaseClass1::foo and the second one as BaseClass2::foo , because when I declare them like this the compiler also sees both as being members of A (which means that the same function is declared twice).
I have some questions about the explicit override feature that I'm hoping you can answer. Here they come:
When you use the explicit override, do you actually select a function for usage in the derived class? E.g., when you write o.foo1(1) , does it give you an error on ambiguity? (I expect that it does.)
And, more importantly, what happens if Intf1 and Intf2 both implement foo1 , and you write o.Intf1::foo1(1) ? Does it call Intf1 's implementation (as it normally would, using static binding)? Or does it call A 's Intf1::foo1 ? I think the construct makes a call like this a bit ambiguous, because A suddenly has both it's own member called Intf1::foo1 (and not simply foo1 , because otherwise it would conflict with the other foo1 !) and an inherited (but overridden) member also identified by the name of Intf1::foo1 .
Here's what I'm thinking: the problem with C++ is that it doesn't allow you to disambiguate these things using renaming. If you would be able to write something like:
class A : public Intf1 (if1_foo1 = foo1), public Intf2
{
void if1_foo1(int a)
{
Intf1::if1_foo1(int a);
}
};
then there would be no problem ever, because whenever you would inherit from multiple base classes you would simply rename everything that was ambiguous. Note the fact that I wrote Intf1::if1_foo1 , while Intf1 doesn't have a member called if1_foo1 . This would only work when you were writing it from A , and it is a very important feature because it fixes all problems with the ambiguities caused by repeated inheritance:
class A : public Intf1 (first_foo1 = foo1), public Intf1 (second_foo1 = foo1)
{
public:
void first_foo1(int a)
{
Intf1::first_foo1(a);
}
};
This would be completely unambiguous! Oh well, why am I speculating about this, it will never happen.
-- Bart
|
|
|
|
|
New features - new problems
To your questions:
> E.g., when you write o.foo1>(1), does it give you
> an error on ambiguity? (I expect that it does.)
Yes you assumed right - the compiler throws the error
'A::foo1' : ambiguous call to overloaded function
> And, more importantly, what happens if Intf1 and
> Intf2 both implement foo1, and you write
> o.Intf1::foo1(1)?
It´s somewhat curious. I expected the compiler to call the implementation of o.Intf1::foo1 but instead it threw a linker error, that the function Intf1:foo1 isn´t implemented.
But how/why it´s an abstract function ?
However static_cast<Intf1&>(o).foo1(1) works as expected and calls the implementation of o.Intf1::foo1
The renaming syntax you had suggested would be nice to have and would prevent ambiguities.
But as you already stated there´s only little hope that it will be part of the c++ standard in the near future.
As a workaround i would suggest the following code:
class A : public Intf1, public Intf2
{
public:
virtual void if1_foo1(int i)
{
static_cast<Intf1&>(*this).foo1(i);
}
virtual void if2_foo1(int i)
{
static_cast<Intf2&>(*this).foo1(i);
}
private:
virtual void Intf1::foo1(int) { cout << "Intf1" << endl; }
virtual void Intf2::foo1(int) { cout << "Intf2" << endl; }
virtual void foo2(int) {}
};
And... i recognized my last answers have been badly (should i say ugly) formatted - i hope this one is more readable
|
|
|
|
|
It´s somewhat curious. I expected the compiler to call the implementation of o.Intf1::foo1 but instead it threw a linker error, that the function Intf1:foo1 isn´t implemented.
But how/why it´s an abstract function?
I think it's because o.Intf1::foo1 actually refers to Intf1::foo1 (which is a pure virtual function) instead of to A 's implementation of Intf1::foo1 . This is exactly what I was wondering about; there is no way to statically identify A 's Intf1::foo1 , because if you write o.Intf1::foo1 you statically refer to Intf1 's implementation of foo1 (a pure virtual function), and if you write o.foo1 you're ambiguous...
And yes, your post is much more readable like this.
-- Bart
|
|
|
|
|
FYI: I was interested to check this out, so I took a look at the final draft for the standard (here) . I concluded that the standard only allows "using A::foo" and nothing like "using A::foo(int)". I've checked the syntax for (member) function declarations in section [dcl.fct.def], and the syntax that VC++ and BCB support doesn't seem to be part of the standard.
-- Bart
|
|
|
|
|
|
No, I haven't. I do do their "bug of the month" every month though. And I have in fact used a similar product (of which I can't recall the name right now).
Seriously, I think that a product like that would (and should) be able to warn on conditions such as these, and it probably does. It might have a rule like "if a derived class defines a member function X and the base class defines member function X as well, and if they have a different parameter list => emit a warning", or "if a derived class defines one or more overloads of a member function X and the base class defines one or more overloads of member function X as well, and if there are overloaded versions found in one but not in the other => emit a warning". When you would be using a product like PC-Lint, the only advantage of using my piece of code would be that you get your warning a bit earlier -- as I don't suppose you would do a full PC-Lint analysis at every compile.
-- Bart
|
|
|
|
|
Anytime you are building a library for your own use and especially for the use of others, if you come across a situation where you find that you need to change the signature of a method, STOP right there. For me a cardinal rule to never break is to change an interface to a class. Instead you add a new method with the new signature. By doing so, that allows all existing code that uses that interface, to continue to work exactly the same as it always did.
As well, your problem description is still unclear to me. I don't understand how adding a parameter with a default value to a method in a base class, stops an inheriter from being able to call the base method. An explicit code example to illustrate this would have been helpful.
Chris Meech
It's much easier to get rich telling people what they want to hear. Chistopher Duncan
But for a man, barbecuing eggplant and portobello mushrooms is a sure way to have people question your sexual orientation. Kuro5hin
|
|
|
|
|