#Alternative to a pure virtual function that doesn't require derived classes to implement it?

160 messages · Page 1 of 1 (latest)

long elkBOT
#

When your question is answered use !solved to mark the question as resolved.

Remember to ask specific questions, provide necessary details, and reduce your question to its simplest form. For tips on how to ask a good question use !howto ask.

fierce sierra
#

i'm not sure i understand the problem

#

just implement the method in the base class to throw whatever you want it to throw?

warm wagon
#

personally I don't understand the desired feature description

fierce sierra
#

that being said, from a design perspective, this sounds majorly sus

#

yeah

warm wagon
#

wanting to error if "the base version is used, and detecting that at compile time", is essentially a non-sense when it comes to virtual functions

fierce sierra
#

either the method exists, or it doesn't

#

you can't have it sorta not exist but it's fine if you use it anyways, except it's not fine, so it'll throw. like wat?

warm wagon
#

one of the key feature of virtual function is that you do not know the concrete type you're manipulating, just that it derives from the base and the base has some virtual functions

#

in those usage contexts, you can not ever know if the base version will be called, or a different one

#

because you don't the most-derived type of instance that you'll manipulate

#

in order to enable that kind of compile-time diagnostic, you must know the concrete, most-derived type

#

if knowing that concrete, most-derived type, is an acceptable constraint/context, you can always opt into using a bunch of templates

#

at which point you will only trigger errors if you try to instantiate a template that tries to use a member function that doesn't exist, and you're free to provide/not provide that member function so long as the bits that need the member function to exist, do not end up being called

#

that last sentence sounds weird but I understand myself, which is something

fierce sierra
#

i understand it. but i'm not sure it'll be very understandable 😛

warm wagon
#

guess it's example time then...

last plover
#

I'm in a legacy code base where virtual functions probably shouldnt have been used at all because everything is known at compile time

#

but im kind of stuck with the pattern

warm wagon
#

;compile

#include <iostream>

template <typename T>
void use_foo(T&& t) { t.foo(); }

template <typename T>
void use_bar(T&& t) { t.bar(); }

struct Fooable
{
  void foo() { std::cout << "only foo!\n"; }
};

struct FooBarable
{
  void foo() { std::cout << "FooBar foo!\n"; }
  void bar() { std::cout << "bar!\n"; }
};


int main()
{
  Fooable f;
  FooBarable fb;

  use_foo(f);
  use_foo(fb);

  use_bar(fb);
  // use_bar(f);  // <-- problem
}
obtuse swanBOT
#
Program Output
only foo!
FooBar foo!
bar!
warm wagon
#

to what extent do you have control over all the derived classes, and what's the current existing thing/interface

last plover
#

I'm able to make new ones but cant really change existing ones

warm wagon
#

if you don't ever actually need/use the polymorphism, you can just work as if you didn't have virtual functions

#

up to having to deal with already existing pure virtual functions that force you to implement them despite them not doing anything

jolly grove
#

I don't want to turn the base class into an abstract class
Why do you not want it to be an abstract class?

last plover
#

The actual problem I'm solving is that I've made some new derived types with some static constexpr metadata on them, and I'm stuck with the getters for that new static metadata being virtual function calls by pointers typed to the base class.

jolly grove
#

But why does the base class need to be concrete?

warm wagon
last plover
#

because I cannot alter existing derived classes to implement the getter for this new metadata and making the base function pure virtual makes them stop working

warm wagon
#

if you know the concrete type then you don't actually end up having to virtual dispatch the thing

jolly grove
#

Do you want to have instances of the base class? Then you could go with the exception throwing version and make sure derived classe overrides do not call into Base::Method.

warm wagon
#

what?

jolly grove
warm wagon
#

the new derived types have the static constexpr stuff, and to abide to the existing interface you have existing virtual getters

#

that you've implemented to return a ref/copy of the static constexpr stuff

#

so why do you need the getters to be pure virtual

#

if you added the getters, why are they virtual in the base

#

and not just non-virtual in the derived types

last plover
warm wagon
#

you said the types are known at compile-time

fierce sierra
#

yeah i don't get that either. you either have that data on all objects of that kind, or you don't.

warm wagon
last plover
#

sorry if I'm being unclear

#

like, if I could rewrite more than I am able to I could just make this compile time

fierce sierra
#

if you know the exact type, why do you not have that exact type?

#

why do you have a base type instead

last plover
warm wagon
#

there's something that's fishy about this whole thing, the preferred way to use polymorphism is to ask the polymorphic type to do the thing, not grab information out of the polymorphic type to do different things

fierce sierra
#

yeah

warm wagon
#

if the getter doesn't make sense for all derived types, then in what context do you actually need to get/grab the information

fierce sierra
#

like, dunno, if you can change neither the interface nor the code that uses the interface then i have no idea what you're hoping to achieve. you're gonna have to change something in order to change something.

warm wagon
#

instead of having a virtual function that let the type do its thing, possibly using its internal data as it wishes to

#

I'm not gonna say that it never happens that you need to downcast or grab information that only makes sense in specific cases

#

but ideally you try to avoid that

#

then if you're stuck with that then you essentially just do it

last plover
#

pseudo code for the front end is

&base pointer =  datastructure.getclass()
pointer.getmetadata()

because the front end interface I've got is that &base pointer, as far as I'm aware, I need to use a virtual function here?

fierce sierra
#

why do you need to get the metadata out of the object in the first place?

warm wagon
#

and how much control do you have on getclass

#

and how specific is this snippet of code

#

if you're in a very specific derived class context, you can use covariant return types

fierce sierra
#

like, whenever you're writing a method whose name starts with get or set, that is a moment to pause and think real hard because you're likely in the process of making a mistake

#

if you're getting or setting data on objects, that means your objects are relegated to being mere data containers. there's no encapsulation anymore. the actual logic of what to do with state is now in the code using the object as opposed to in the object.

last plover
#

this is some weird legacy stuff with passing data from a c++ backend to a c# front end with managed c++ so like, the backend c++ stuff is basically just a data container yeah

fierce sierra
#

if it's basically just a data container then why the polymorphism?

last plover
#

I can't change design decisions from 20 years ago here without rewriting everything

#

tl;dr

fierce sierra
#

well, we're gonna have to understand what problem you're trying to solve

#

otherwise, there's nothing we can do to help you

last plover
#

Ok so this snippet

&base pointer =  datastructure.getclass()
pointer.getmetadata()
#

.getclass is always going to return a pointer to a derived type that implements the new function

fierce sierra
#

well yeah, then that function will have to be virtual 🤷‍♂️

#

which means all derived classes will have to implement it

last plover
#

ideally I'd be able to just get a pointer directly to the derived type here instead and skip virtuals all together

fierce sierra
#

do you know for sure that this is always a specific type in this case?

warm wagon
#

ok but what is getclass/datastructure

last plover
warm wagon
#

if datastructure for whatever reason could return a variety of derived type, you cannot just assume you'll always be in a context to be able to just call getmetadata

fierce sierra
#

like, given that you're already royally screwed design-wide, you could just dynamic_cast

#

which ofc is utterly terrible

#

but here we are

last plover
fierce sierra
#

if it's completely not an option for you to change anything then dynamic_cast is the only thing you can do

last plover
#

yes I was considering a dynamic cast

warm wagon
fierce sierra
#

ofc that only makes everything worse

#

but the alternative will be actually fixing this design

#

if you say you can't do that, well…

last plover
#

so theres the base class
theres a bunch of derived types
then theres this final layer of derivation where I take some of those derived types and give them some extra static metadata

all types with the extra metadata are derived from types that are identical except for lacking that metadata, and all those derived types are part of the existing inheritance tree

fierce sierra
#

why does the metadata need to be attached by deriving from the types without metadata?

#

you could instead have, e.g., a wrapper template that just adds that metadata around a given type

#

and then that template could inherit from some base class interface that's shared by all wrapped types

#

and so instead of making the original hierarchy even worse than it already is, you've made a separate hierarchy for stuff with metadata

#

no idea if that would work in your case ofc

#

since i have no idea what your stuff is doing

last plover
#

If there's a better way to do it then I'm open to hearing it!

Currently the derived types have 1-3 kinds of valid metadata for them, and there are many many instances and in the current design the metadata is stored per instance. This has become a genuine performance problem from storing thousands of redundant strings.

I made a template that derives from the existing derived types and accepts the constexpr metadata strings as template parameters so I can move that metadata from being per isntance to being static.

#

I did this because the rest of the code base doesnt seem to notice that change and it quite happily works with the new derived types with static metadata as it did with the old system where its per instnace.

fierce sierra
#

so you have tons of instances that all have the same metadata?

#

have you considered, e.g., instead of attaching the metadata to each instance, just having a dictonary of metadata keyed on the pointer to the object?

last plover
#

well what I'm currently doing instead is making a derived type per metadata (with the template) and defining the metadata as compile time static constant in those types.

fierce sierra
#

but you said the original design already had metadata per object

#

so how was that metadata accessed?

last plover
#

the original design has metadata per instance and used the getters in the base class

fierce sierra
#

well then continue doing that?

#

just implement the getters such that they return your string literal or smth instead of a per-class copy of a string 🤷‍♂️

last plover
#

so everything works with the templated solution, I'm just trying to remove the implementation of the getter from the base class so it stops being used and if someone does use it then the compiler complains at them if they did it in a static context and they get runtime errors if they try it otherwise.

#

i.e you shouldnt be trying to access the metadata unless you're working with a derived type that has metadata defined in it

fierce sierra
#

i don't understand

#

the existing system has a getter for the metadata

#

and is using that

#

so just keep using that?

#

if the problem you're trying to solve is move from per-instance to per-class metadata, just change the implementation of the getter accordingly

#

and you're done

#

anything else will require exactly the kind of changes you insist you cannot make

last plover
#

so the existing system has a virtual getter on the base class, and I'm still using that. but I want to assert that when that virtual function is called that it resolves to a derived implementation of it, not the base one.

#

because the base one just returns a null pointer now

fierce sierra
#

well then assert(false) in the base implementation 🤷‍♂️

jolly grove
#

but I want to assert that when that virtual function is called that it resolves to a derived implementation of it, not the base one.
When you call the virtual function it will already only call the derived implementation?

last plover
#

because for all I know someone could create an instance of the base class directly and then they cause a problem where the front end tries to access non existant metadata

jolly grove
jolly grove
fierce sierra
last plover
#

Ok so that is the best way to go ahead in this instance?

Ok sorry, I think I got a bit stubborn on this because it seemed like there would be something more elegant to do than that.

fierce sierra
#

this design is already utter crap. there's no elegance to be found there i'm afraid ^^

last plover
#

but importantly isnt trying to access metadata in that context

#

and shouldnt be either

fierce sierra
#

lol

jolly grove
#
void Base::Method()
{
    throw std::exception{ "not implemented; Method can only be called on derived classes that override it."};
}
last plover
#

but I need it to throw an error if they try

fierce sierra
#

yeah this is terrible. but there's pretty much nothing you can do without completely changing that design.

#

yeah, assert(false);

last plover
#

ok cool, if that is indeed the case I shall just assert then

fierce sierra
#

if you only have a base type to work with, there's generally nothing you can do at compile time

#

as @warm wagon explained

#

your code is inherently built on resolving things at runtime

last plover
#

ideally I'd have made a metadata wrapper and put the existing types in that wrapper but I'd need to rewrite so much stuff to implement that that it wouldnt be worth it

jolly grove
last plover
#

the templating solution I'm using is just for transparency with respect to the existing code base

#

since everything "looks" the same to the front end and I'm effectively just changing an implementation detail

#

Actually, I might be able to make a metadata wrapper class and then have the front end create pointers to that metadata wrapper instead 🤔

last plover
jolly grove
warm wagon
#

well

#

that's not exactly something you can rely on if you don't know the actual context/architecture

#

especially because they ship polymorphic types

jolly grove
warm wagon
#

there's generally nothing that prevents a client that also codes to derive from your interfaces and inject their own derived types in your code

#

because that's one of the point of polymorphic types

#

well maybe not one of the "point" but one way they are often used

jolly grove
#

Conditional 'final' needed.

long elkBOT
#

@last plover Has your question been resolved? If so, type !solved :)

last plover
#

!solved

long elkBOT
#

Thank you and let us know if you have any more questions!

This thread is now set to auto-hide after an hour of inactivity

spare root
#

assert doesn't exist in release builds

fierce sierra
#

lol