How to achieve “virtual template function” in C++

后端 未结 9 1620
囚心锁ツ
囚心锁ツ 2020-11-28 03:20

first off: I have read and I know now that a virtual template member function is not (yet?) possible in C++. A workaround would be to make the class a template and then use

相关标签:
9条回答
  • 2020-11-28 03:50

    You can create a template class with virtual function, and implement the function in the derived class without using template in the follwing way:

    a.h:
    
    template <class T>
    class A
    {
    public:
        A() { qDebug() << "a"; }
    
        virtual A* Func(T _template) { return new A;}
    };
    
    
    b.h:
    
    class B : public A<int>
    {
    public:
        B();
        virtual A* Func(int _template) { return new B;}
    };
    
    
    and the function CTOR and call 
    
      A<int>* a1=new B;
        int x=1;
        a1->Func(x);
    

    unfortunately i havn't found a way to create a virtual function with template parameters without declaring the class as a template and it template type on the dervied class

    0 讨论(0)
  • 2020-11-28 03:51

    Per Mikael's post, I have made another offshoot, using the CRTP and following Eigen's style of using derived() for an explicit subclass reference:

    // Adaptation of Visitor Pattern / CRTP from:
    // http://stackoverflow.com/a/5872633/170413
    
    #include <iostream>
    using std::cout;
    using std::endl;
    
    class Base {
    public:
      virtual void tpl(int x) = 0;
      virtual void tpl(double x) = 0;
    };
    
    // Generics for display
    template<typename T>
    struct trait {
      static inline const char* name() { return "T"; }
    };
    template<>
    struct trait<int> {
      static inline const char* name() { return "int"; }
    };
    template<>
    struct trait<double> {
      static inline const char* name() { return "double"; }
    };
    
    // Use CRTP for dispatch
    // Also specify base type to allow for multiple generations
    template<typename BaseType, typename DerivedType>
    class BaseImpl : public BaseType {
    public:
      void tpl(int x) override {
        derived()->tpl_impl(x);
      }
      void tpl(double x) override {
        derived()->tpl_impl(x);
      }
    private:
      // Eigen-style
      inline DerivedType* derived() {
        return static_cast<DerivedType*>(this);
      }
      inline const DerivedType* derived() const {
        return static_cast<const DerivedType*>(this);
      }
    };
    
    // Have Child extend indirectly from Base
    class Child : public BaseImpl<Base, Child> {
    protected:
      friend class BaseImpl<Base, Child>;
      template<typename T>
      void tpl_impl(T x) {
        cout << "Child::tpl_impl<" << trait<T>::name() << ">(" << x << ")" << endl;
      }
    };
    
    // Have SubChild extend indirectly from Child
    class SubChild : public BaseImpl<Child, SubChild> {
    protected:
      friend class BaseImpl<Child, SubChild>;
      template<typename T>
      void tpl_impl(T x) {
        cout << "SubChild::tpl_impl<" << trait<T>::name() << ">(" << x << ")" << endl;
      }
    };
    
    
    template<typename BaseType>
    void example(BaseType *p) {
      p->tpl(2);
      p->tpl(3.0);
    }
    
    int main() {
      Child c;
      SubChild sc;
    
      // Polymorphism works for Base as base type
      example<Base>(&c);
      example<Base>(&sc);
      // Polymorphism works for Child as base type
      example<Child>(&sc);
      return 0;
    }
    

    Output:

    Child::tpl_impl<int>(2)
    Child::tpl_impl<double>(3)
    SubChild::tpl_impl<int>(2)
    SubChild::tpl_impl<double>(3)
    SubChild::tpl_impl<int>(2)
    SubChild::tpl_impl<double>(3)
    

    This snippet may be found in source here: repro:c808ef0:cpp_quick/virtual_template.cc

    0 讨论(0)
  • 2020-11-28 03:53

    Virtual template function is not allowed. However you can use one OR the other here.

    You could make an interface using virtual methods and implement your various animals in terms of having an eating interface. (i.e. PIMPL)

    Less human intuitive would be having a non-member non-friend template function as a free function which could take templated const reference to any animal and make them eat accordingly.

    For the record you don't need templates here. Pure virtual abstract method on the base class is enough to force and interface where all animals must eat and define how they do so with an override, providing a regular virtual would be enough to say all animals can eat but if they don't have a specific way then they can use this default way.

    0 讨论(0)
  • 2020-11-28 03:58

    In you scenario, you are trying to mix compile time polymorphism with runtime polymorphism, but it cannot be done in this "direction".

    Essential, your AMOUNT template argument represents an expected interface for the type to implement based on the union of all the operations each implementation of eat uses. If you where to create an abstract type that declared each of those operations making them virtual where needed, then you could call eat with different types (that derived from your AMOUNT interface). And it would behave as expected.

    0 讨论(0)
  • 2020-11-28 04:00

    Obviously, virtual member function templates are not allowed and could not be realized even theoretically. To build a base class' virtual table, there needs to be a finite number of virtual function-pointer entries. A function template would admit an indefinite amount of "overloads" (i.e. instantiations).

    Theoretically-speaking, a language (like C++) could allow virtual member function templates if it had some mechanism to specify the actual (finite) list of instantiations. C++ does have that mechanism (i.e. explicit template instantiations), so I guess it could be possible to do this in a newer C++ standard (although I have no idea what trouble it would entail for compiler vendors to implement this feature). But, that's just a theoretical discussion, in practice, this is simply not allowed. The fact remains, you have to make the number of virtual functions finite (no templates allowed).

    Of course, that doesn't mean that class template cannot have virtual functions, nor does it mean that virtual functions cannot call function templates. So, there are many solutions in that vein (like the Visitor pattern or other schemes).

    One solution that, I think, serves your purpose (although it is hard to comprehend) elegantly is the following (which is basically a visitor pattern):

    #include <iostream>
    #include <vector>
    
    struct Eater { 
      virtual void operator()(int amount) const = 0;
      virtual void operator()(double amount) const = 0;
    };
    
    template <typename EaterType>
    struct Eater_impl : Eater {
      EaterType& data;
      Eater_impl(EaterType& aData) : data(aData) { };
      virtual void operator()(int amount) const { data.eat_impl(amount); };
      virtual void operator()(double amount) const { data.eat_impl(amount); };
    };
    
    class Animal {
      protected:
        Animal(Eater& aEat) : eat(aEat) { };
      public:
        Eater& eat;
        virtual ~Animal() { delete &eat; };
    };
    
    class Wolf : public Animal {
      private:
        template< class AMOUNT >
        void eat_impl( AMOUNT amount) const { 
          std::cout << "I eat like a wolf!" << std::endl; 
        }
    
      public:
        friend struct Eater_impl<Wolf>;        
        Wolf() : Animal(*(new Eater_impl<Wolf>(*this))) { };
        virtual ~Wolf() { };
    };
    
    class Fish : public Animal {
      private:
        template< class AMOUNT >
        void eat_impl( AMOUNT amount) const { 
          std::cout << "I eat like a fish!" << std::endl; 
        }
      public:
        friend struct Eater_impl<Fish>;
        Fish() : Animal(*(new Eater_impl<Fish>(*this))) { };
        virtual ~Fish() { };
    };
    
    int main() {
      std::vector<Animal*> animals;
      animals.push_back(new Wolf());
      animals.push_back(new Fish());
    
      for (std::vector<Animal*>::const_iterator it = animals.begin(); it != animals.end(); ++it) {
        (*it)->eat(int(0));
        (*it)->eat(double(0.0));
        delete *it;
      };
    
      return 0;
    };
    

    The above is a neat solution because it allows you to define a finite number of overloads that you want in one place only (in the Eater_impl class template) and all you need in the derived class is a function template (and possibly additional overloads, for special cases). There is, of course, a bit of overhead, but I guess that a bit more thought could be put into it to reduce the overhead (additional reference storage and dynamic allocation of Eater_impl). I guess, the curiously recurring template pattern could probably be employed somehow to this end.

    0 讨论(0)
  • 2020-11-28 04:10

    After some thinking I recognized this as the classic multi-method requirement, i.e. a method that dispatches based on the runtime type of more than one parameter. Usual virtual functions are single dispatch in comparison (and they dispatch on the type of this only).

    Refer to the following:

    • Andrei Alexandrescu has written (the seminal bits for C++?) on implementing multi-methods using generics in 'Modern C++ design'
      • Chapter 11: "Multimethods" - it implements basic multi-methods, making them logarithmic (using ordered typelists) and then going all the way to constant-time multi-methods. Quite powerful stuff !
    • A codeproject article that seems to have just such an implementation:
      • no use of type casts of any kind (dynamic, static, reinterpret, const or C-style)
      • no use of RTTI;
      • no use of preprocessor;
      • strong type safety;
      • separate compilation;
      • constant time of multimethod execution;
      • no dynamic memory allocation (via new or malloc) during multimethod call;
      • no use of nonstandard libraries;
      • only standard C++ features is used.
    • C++ Open Method Compiler, Peter Pirkelbauer, Yuriy Solodkyy, and Bjarne Stroustrup
    • The Loki Library has A MultipleDispatcher
    • Wikipedia has quite a nice simple write-up with examples on Multiple Dispatch in C++.

    Here is the 'simple' approach from the wikipedia article for reference (the less simple approach scales better for larger number of derived types):

    // Example using run time type comparison via dynamic_cast
    
    struct Thing {
        virtual void collideWith(Thing& other) = 0;
    }
    
    struct Asteroid : Thing {
        void collideWith(Thing& other) {
            // dynamic_cast to a pointer type returns NULL if the cast fails
            // (dynamic_cast to a reference type would throw an exception on failure)
            if (Asteroid* asteroid = dynamic_cast<Asteroid*>(&other)) {
                // handle Asteroid-Asteroid collision
            } else if (Spaceship* spaceship = dynamic_cast<Spaceship*>(&other)) {
                // handle Asteroid-Spaceship collision
            } else {
                // default collision handling here
            }
        }
    }
    
    struct Spaceship : Thing {
        void collideWith(Thing& other) {
            if (Asteroid* asteroid = dynamic_cast<Asteroid*>(&other)) {
                // handle Spaceship-Asteroid collision
            } else if (Spaceship* spaceship = dynamic_cast<Spaceship*>(&other)) {
                // handle Spaceship-Spaceship collision
            } else {
                // default collision handling here
            }
        }
    }
    

    0 讨论(0)
提交回复
热议问题