Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Prohibit calling a function with a derived class object

35 views
Skip to first unread message

Paavo Helde

unread,
Jul 21, 2016, 9:35:40 AM7/21/16
to
This came up during code refactoring. Let's assume I have a function
taking a reference to a base class object, say

void foo(Base&);

What would be the best way to refuse compilation when a derived class
object is passed instead? (I am needing this to avoid logical constness
leaking problems in a callback scenario, if anybody is curious).

There are other functions where passing both Base and Derived is needed
and is perfectly OK, the prohibition should appear only for foo() only.

My first attempt on this only reports a linker error, which is not
enough as it does not provide the location of the invalid call (and
there are thousands of those calls in the codebase). I would prefer to
get a compiler error at the line marked "NOT OK":

class Base {};
class Derived: public Base {};

void foo(Base& ref) {}

void foo(Derived&); // not implemented

int main() {
Derived der1;
foo(der1); // compiling, NOT OK!
}

TIA
Paavo


Chris Vine

unread,
Jul 21, 2016, 10:19:27 AM7/21/16
to
If you want a compile time assertion rather than a run-time one, I
think you will have to templatize it and apply static_assert to the
result of applying std::is_same to the type passed in.

Chris

Alf P. Steinbach

unread,
Jul 21, 2016, 12:24:34 PM7/21/16
to
Make that

void foo( Derived& ) = delete;

to get diagnostics at compile time rather than link time.


> int main() {
> Derived der1;
> foo(der1); // compiling, NOT OK!
> }

The approach won't catch Base references where the most derived class is
something derived from Base.

And you can't do that statically.

One way to do that at runtime is to make Base a polymorphic class, and
`assert( typeid( object ) == typeid( Base ) )`.

That said, this all has a certain odour.

But dealing with constness in callbacks is notoriously difficult.


Cheers & hth.,

- Alf

Mr Flibble

unread,
Jul 21, 2016, 12:47:37 PM7/21/16
to
What you are trying to do breaks the Liskov substitution principle so is
the wrong approach; try to solve your problem some other way.

/Flibble

Chris Vine

unread,
Jul 21, 2016, 1:11:39 PM7/21/16
to
On Thu, 21 Jul 2016 18:22:35 +0200
"Alf P. Steinbach" <alf.p.stein...@gmail.com> wrote:
> On 21.07.2016 15:35, Paavo Helde wrote:
> > This came up during code refactoring. Let's assume I have a function
> > taking a reference to a base class object, say
> >
> > void foo(Base&);
> >
> > What would be the best way to refuse compilation when a derived
> > class object is passed instead? (I am needing this to avoid logical
> > constness leaking problems in a callback scenario, if anybody is
> > curious).
> >
> > There are other functions where passing both Base and Derived is
> > needed and is perfectly OK, the prohibition should appear only for
> > foo() only.
> >
> > My first attempt on this only reports a linker error, which is not
> > enough as it does not provide the location of the invalid call (and
> > there are thousands of those calls in the codebase). I would prefer
> > to get a compiler error at the line marked "NOT OK":
> >
> > class Base {};
> > class Derived: public Base {};
> >
> > void foo(Base& ref) {}
> >
> > void foo(Derived&); // not implemented
>
> Make that
>
> void foo( Derived& ) = delete;
>
> to get diagnostics at compile time rather than link time.

How interesting. I didn't know you could delete functions which are
not class member functions in order to prevent unwanted implicit
conversions. I guess it works because the deleted function still
participates in overload resolution.

I am not sure I would want to do that often, but it is nice to know.

Paavo Helde

unread,
Jul 21, 2016, 3:21:14 PM7/21/16
to
On 21.07.2016 19:22, Alf P. Steinbach wrote:
> On 21.07.2016 15:35, Paavo Helde wrote:
>> This came up during code refactoring. Let's assume I have a function
>> taking a reference to a base class object, say
>>
>> void foo(Base&);
>>
>> What would be the best way to refuse compilation when a derived class
>> object is passed instead? (I am needing this to avoid logical constness
>> leaking problems in a callback scenario, if anybody is curious).
>>
>> There are other functions where passing both Base and Derived is needed
>> and is perfectly OK, the prohibition should appear only for foo() only.
>>
>> My first attempt on this only reports a linker error, which is not
>> enough as it does not provide the location of the invalid call (and
>> there are thousands of those calls in the codebase). I would prefer to
>> get a compiler error at the line marked "NOT OK":
>>
>> class Base {};
>> class Derived: public Base {};
>>
>> void foo(Base& ref) {}
>>
>> void foo(Derived&); // not implemented
>
> Make that
>
> void foo( Derived& ) = delete;
>
> to get diagnostics at compile time rather than link time.

Wow, that looks exactly what I wanted! And it seems it is even supported
by my target compilers!

Another approach what I came up with meanwhile was to make foo( Derived&
) an inaccessible private member function. But "=delete" feels cleaner
(documents the intent better).

Thanks!
Paavo

Paavo Helde

unread,
Jul 21, 2016, 4:06:50 PM7/21/16
to
The Liskov principle does not work here because there is a callback
mechanism involved. A class is supposed to register a bunch of Base
object addresses (via the function similar to foo() above). The engine
later stores some Base objects there and calls back a virtual function
of the class to process them.

Now if the class registered Derived class objects instead of Base class
objects, the stored objects would be actually of a wrong type (not so
big deal in my case as this would just mean some loss of const
correctness, but still).

The data cannot be passed via the normal virtual function parameters
because there are many different types involved and the number of
potential signatures is large. Passing an array of some Variant type
would work, but this would make the class code clumsier and more fragile
than necessary.

Cheers
Paavo



Mr Flibble

unread,
Jul 21, 2016, 4:25:38 PM7/21/16
to
The Liskov substitution principle always works if you follow it. You
are DOING IT WRONG (TM).

/Flibble

Paavo Helde

unread,
Jul 21, 2016, 6:42:04 PM7/21/16
to
On 21.07.2016 23:25, Mr Flibble wrote:
> The Liskov substitution principle always works if you follow it. You
> are DOING IT WRONG (TM).

Liskov principle says that a derived class must always be fully
functional when accessed via base class interface. That's no problem
with my classes. Instead, my problem is that because of callback
mechanism the base class object might be accessed via derived class
interface, and Liskov principle does not say anything about this.

To clarify: my Base and Derived correspond roughly to
std::vector::const_iterator and std::vector::iterator. Derived is
derived from Base so that casting a pointer/reference of Derived to a
pointer/reference of Base would always work seamlessly/automatically,
*including* with arrays of Derived (I am guaranteeing
sizeof(Derived)==sizeof(Base)). That's Liskov in work, BTW - a mutable
object can always be accessed via a non-mutable interface as well.

However, regarding arrays of derived class objects - this is where C++
fails hard if the sizes are not the same (and I suspect it might be
formally UB even if sizes are the same(?)). So it appears C++ itself has
some troubles with supporting Liskov (in arrays of objects).

Here is some example code about what I mean by a Liskov substitution of
an array:

#include <vector>

void foo(std::vector<int>::const_iterator* array, int len) {
int x = *(array[len-1]);
}

int main() {
std::vector<int> test = {1, 2, 3};
std::vector<int>::iterator arr[3] =
{test.begin(), test.begin()+1, test.begin()+2};
foo(arr, 3);
}

Curiously, MSVC derives std::vector::iterator from
std::vector::const_iterator so it compiles and runs this example fine,
but g++ does not (effectively failing Liskov):

main.cpp: In function `int main()':
main.cpp:33:12: error: cannot convert `std::vector<int>::iterator* {aka
__gnu_cxx::__normal_iterator<int*, std::vector<int> >*}' to
`std::vector<int>::const_iterator* {aka
__gnu_cxx::__normal_iterator<const int*, std::vector<int> >*}' for
argument `1' to `void foo(std::vector<int>::const_iterator*, int)'
foo(arr, 4);

Jerry Stuckle

unread,
Jul 21, 2016, 9:04:29 PM7/21/16
to
On 7/21/2016 9:35 AM, Paavo Helde wrote:
> This came up during code refactoring. Let's assume I have a function
> taking a reference to a base class object, say
>
> void foo(Base&);
>
> What would be the best way to refuse compilation when a derived class
> object is passed instead? (I am needing this to avoid logical constness
> leaking problems in a callback scenario, if anybody is curious).
>

If you need this, then you don't understand inheritance.

--
==================
Remove the "x" from my email address
Jerry Stuckle
jstu...@attglobal.net
==================
0 new messages