Specialize inherited templated functions with the current class

Your best bet is to skip the code and read the comments for a quick introduction to what I am trying to do, however there are more important details I should try to describe:

I want to add a member function to a class that is inherited through the class hierarchy. I want to make sure that derived classes don't need to rewrite any code or do anything special to get this function automatically, and I would like to avoid macros. This function must also be inherited by any subclasses made after compilation (this is part of the platform that other people will distribute).

A function is a debug function that keeps track of configured smart pointers (like std :: shared_ptr) pointing to class instances - I want to know why a given instance is kept in memory, which means I need to know what pointers are pointing to it. Individual smart pointers are templates, and I want to ensure that the specialization of the smart pointer used as an argument matches the type of the instance's static type.

Since this is in a framework, it is reasonably important to enable this feature for people expanding the hierarchy after compilation.

This compiles on Windows with Visual Studio 2010. We'll be moving to VS 2015 shortly, but I'd rather not wait. We're also mostly cross platform (we disable a couple of things for other platforms only when needed). If there is a solution that requires C ++ 11/14/17 / later, I would still like to hear it out of interest.

I feel like I need to make the function templated and specialized at the same time, specializing in some kind of self, but I have no idea how to make subclasses get what is essentially copied and not inherited, I also understand (I think) that this function is not suitable for creating virtual ones, but I also want the subclass not to receive specialization from parent classes.

I think there are several options I can go for, but I would like to know if there is something better that I have not thought of:

  • Just give up trying to get that kind of safety.
  • Use dynamic check instead - I haven't figured out how to do this yet. I think it will be easier, but less secure, and I like the security.
  • Using a Macro to automatically generate a typdef for Self, ala https://stackoverflow.com/a/2996999
    • I don't know exactly how it works. Anyway, this is only half of the problem, and I don't think it will bring me any closer to solving the declare / inherit-but-not-really puzzle function.
    • Also not a fan of forcing people to use a macro, especially for something that's just for debugging.

I fully expect that a completely different design pattern will be the best solution, but such a solution has not yet crossed my mind.

I do think the compiler will be able to find out whatever I need to support this, but I'm not sure what the language is doing.

I also really appreciate some help or feedback on how to best frame this question. It’s not easy for me to figure out the problem myself, let alone understand it for other people.

template<class T>
class CustomSmartPointer {};

class Base {
public:
    void doSomething(CustomSmartPointer<Base> arg) {} //Should be able to say something like CustomSmartPointer<Self> instead, where the compiler knows what I mean by Self
};

class Derived : public Base {
public:
    void doSomething(CustomSmartPointer<Derived> arg) {} //Shouldn't have to specify this declaration, or code, again, as it is direcly copy-pastable from above
};

class Derived2 : public Base {};

void main() {
    Base b;
    Derived d;
    Derived2 d2;

    CustomSmartPointer<Base> cb;
    CustomSmartPointer<Derived> cd;

    b.doSomething(cb);

    d.doSomething(cd);

    d2.doSomething(cb); //This shouldn't compile, as cb is the wrong type for the specialisation that should exist for Derived2::doSomething
}

      

+3


source to share


3 answers


I'll repeat my comment that explains why inheritance inheritance is not appropriate: void doSomething(CustomSmartPointer<Base> arg)

and void doSomething(CustomSmartPointer<Derived> arg)

are unrelated member functions (although they are overloads with the same name internally Derived

). If you do not want to void doSomething(CustomSmartPointer<Base> arg)

be available in Derived

, then execution inheritance is not suitable in your case.

CRTP is probably what you are looking for.

template <class T> 
struct Base {
    void doSomething(CustomSmartPointer<T>);
};

struct Derived: Base<Derived> {}; // will inherit void doSomething(CustomSmartPointer<Derived>) from parent
struct Derived2: Base<Derived2> {}; // will inherit void doSomething(CustomSmartPointer<Derived2>) from parent

      



Or course, this means that derived classes will not have a common parent. You can give a Base

non-template parent to fix this, but you cannot use it as an interface for doSomething

.

On the other hand, if you cannot change Base

, but Derived

must inherit Base

, then there is no way to escape the inherited member function, and if it is a mistake to call it, it will be hard to catch at compile time. However, you can override doSomething(CustomSmartPointer<Base>)

in Derived

and throw an exception at runtime to catch runtime errors.

+2


source


A possible way to achieve this could be CRTP , although it might look ugly and make inheritance difficult:



template<class T>
class CustomSmartPointer {};

template <class D>
class Base {
public:
    void doSomething(CustomSmartPointer<D> arg) {} 
};

class Derived : public Base<Derived> {};

class Derived2 : public Base<Derived2> {};

int main() {
    Derived d;
    Derived2 d2;

    CustomSmartPointer<Derived> cd;

    d.doSomething(cd);

    d2.doSomething(cd); //does not compile
    return 0;
}

      

+1


source


What happens when you complicate a C ++ file with templates is that the compiler creates a specialized template function when needed. (This is instantiated from here

in your mistakes). in your case the compiler will create two classes: CustomSmartPointerBase

and CustomSmartPointerDerived

. Now you create a function in the database: doSomething(CustomSmartPointerBase)

. This function is also available in the class Derived

because it is a subclass. The declaration doSomething

in Derived

does not override the value from Base

, because it takes a different type as an argument, so it is overloaded.

This means that the class Derived

has two functions in it: a doSomething

for any type CustomSmartPointer

.

You can try this by adding cout << "doSomethingBase" << endl;

and operators cout << "doSomethingDerived" <<endl;

to the appropriate doSomething

fuctions and running the code. Or better, walk through it with a debugger.

0


source







All Articles