简体   繁体   中英

C++ Templated Virtual Function

Templated virtual member functions are not supported in C++ but I have a scenario where it would be ideal. Im wondering if someone has ideas for ways to accomplish this.

#include <iostream>


class Foo {
public:
    virtual void bar(int ){}
    // make a clone of my existing data, but with a different policy
    virtual Foo* cloneforDB() = 0;
};


struct DiskStorage {
    static void store(int x) { std::cout << "DiskStorage:" << x << "\n"; }
};

struct DBStorage {
    static void store(int x) { std::cout << "DBStorage:" << x << "\n"; }
};

template<typename Storage>
class FooImpl : public Foo {
public:
    FooImpl():m_value(0) {}
    template<typename DiffStorage>
    FooImpl(const FooImpl<DiffStorage>& copyfrom) {
        m_value = copyfrom.m_value;
    }
    virtual void bar(int x) {
        Storage::store(m_value);
        std::cout << "FooImpl::bar new value:" << x << "\n";
        m_value = x;
    }
    virtual Foo* cloneforDB() {
        FooImpl<DBStorage> * newfoo = new FooImpl<DBStorage>(*this);
        return newfoo;
    }
    int m_value;
};

int main()
{
    Foo* foo1 = new FooImpl<DiskStorage>();
    foo1->bar(5);
    Foo* foo2 = foo1->cloneforDB();
    foo2->bar(21);
}

Now if I want to clone the Foo implmemetation, but with a different Storagepolicy, I have to explicitly spell out each such implementation:

cloneforDB()
cloneforDisk()

A template parameter would have simplified that. Can anyone think of a cleaner way to do this? Please focus on the idea and not the example, since its obviously a contrived example.

Usually if you want to use a virtual template method, it means that something is wrong in the design of your class hierarchy. The high level reason for that follows.

Template parameters must be known at compile-time, that's their semantics. They are used to guarantee soundness properties of your code.

Virtual functions are used for polymorphism, ie. dynamic dispatching at runtime.

So you cannot mix static properties with runtime dispatching, it does not make sense if you look at the big picture.

Here, the fact that you store something somewhere should not be part of the type of your method, since it's just a behavioral trait, it could change at runtime. So it's wrong to include that information in the type of the method.

That's why C++ does not allow that: you have to rely on polymorphism to achieve such a behavior.

One easy way to go would be to pass a pointer to a Storage object as an argument (a singleton if you just want one object for each class), and work with that pointer in the virtual function.

That way, your type signature does not depend on the specific behavior of your method. And you can change your storage (in this example) policy at runtime, which is really what you should ask for as a good practice.

Sometimes, behavior can be dictated by template parameters (Alexandrescu's policy template parameters for example), but it is at type-level, not method level.

Just use templates all the way:

class Foo {
public:
    virtual void bar(int ){}

    template <class TargetType>
    Foo* clonefor() const;
};

class FooImpl { ... };

template 
inline <class TargetType>
Foo* Foo::clonefor() const
{
    return new FooImpl<TargetType>(*this);
}

Now call it:

int main()
{
    Foo* foo1 = new FooImpl<DiskStorage>();
    foo1->bar(5);
    Foo* foo2 = foo1->clonefor<DBStorage>();
    foo2->bar(21);
}

A trick I have sometimes used to get around this issue is this:

template<typename T>
using retval = std::vector<T const*>;
struct Bob {};

// template type interface in Base:
struct Base {
  template<typename T>
  retval<T> DoStuff();

  virtual ~Base() {};

// Virtual dispatch so children can implement it:
protected:
  virtual retval<int> DoIntStuff() = 0;
  virtual retval<double> DoDoubleStuff() = 0;
  virtual retval<char> DoCharStuff() = 0;
  virtual retval<Bob> DoBobStuff() = 0;
};

// forward template interface through the virtual dispatch functions:
template<> retval<int> Base::DoStuff<int>() { return DoIntStuff(); }
template<> retval<double> Base::DoStuff<double>() { return DoDoubleStuff(); }
template<> retval<char> Base::DoStuff<char>() { return DoCharStuff(); }
template<> retval<Bob> Base::DoStuff<Bob>() { return DoBobStuff(); }

// CRTP helper so the virtual functions are implemented in a template:
template<typename Child>
struct BaseHelper: public Base {
private:
  // In a real project, ensuring that Child is a child type of Base should be done
  // at compile time:
  Child* self() { return static_cast<Child*>(this); }
  Child const* self() const { return static_cast<Child const*>(this); }
public:
  virtual retval<int> DoIntStuff() override final { self()->DoStuff<int>(); }
  virtual retval<double> DoDoubleStuff() override final { self()->DoStuff<double>(); }
  virtual retval<char> DoCharStuff() override final { self()->DoStuff<char>(); }
  virtual retval<Bob> DoBobStuff() override final { self()->DoStuff<Bob>(); }
};

// Warning: if the T in BaseHelper<T> doesn't have a DoStuff, infinite
// recursion results.  Code and be written to catch this at compile time,
// and I would if this where a real project.
struct FinalBase: BaseHelper<FinalBase> {
  template<typename T>
  retval<T> DoStuff() {
    retval<T> ret;
    return ret;
  }
};

where I go from template-based dispatch, to virtual function dispatch, back to template based dispatch.

The interface is templated on the type I want to dispatch on. A finite set of such types are forwarded through a virtual dispatch system, then redispatched at compile time to a single method in the implementation.

I will admit this is annoying, and being able to say "I want this template to be virtual, but only with the following types" would be nice.

The reason why this is useful is that it lets you write type-agnostic template glue code that operates on these methods uniformly without having to do stuff like pass through pointers to methods or the like, or write up type-trait bundles that extract which method to call.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM