Can a class member function template be virtual?

前端 未结 13 954
旧时难觅i
旧时难觅i 2020-11-22 03:29

I have heard that C++ class member function templates can\'t be virtual. Is this true?

If they can be virtual, what is an example of a scenario in which one would

相关标签:
13条回答
  • 2020-11-22 03:59

    No, template member functions cannot be virtual.

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

    In the other answers the proposed template function is a facade and doesn't offer any practical benefit.

    • Template functions are useful for writing code only once using different types.
    • Virtual functions are useful for having a common interface for different classes.

    The language doesn't allow virtual template functions but with a workaround it is possible to have both, e.g. one template implementation for each class and a virtual common interface.

    It is however necessary to define for each template type combination a dummy virtual wrapper function:

    #include <memory>
    #include <iostream>
    #include <iomanip>
    
    //---------------------------------------------
    // Abstract class with virtual functions
    class Geometry {
    public:
        virtual void getArea(float &area) = 0;
        virtual void getArea(long double &area) = 0;
    };
    
    //---------------------------------------------
    // Square
    class Square : public Geometry {
    public:
        float size {1};
    
        // virtual wrapper functions call template function for square
        virtual void getArea(float &area) { getAreaT(area); }
        virtual void getArea(long double &area) { getAreaT(area); }
    
    private:
        // Template function for squares
        template <typename T>
        void getAreaT(T &area) {
            area = static_cast<T>(size * size);
        }
    };
    
    //---------------------------------------------
    // Circle
    class Circle : public Geometry  {
    public:
        float radius {1};
    
        // virtual wrapper functions call template function for circle
        virtual void getArea(float &area) { getAreaT(area); }
        virtual void getArea(long double &area) { getAreaT(area); }
    
    private:
        // Template function for Circles
        template <typename T>
        void getAreaT(T &area) {
            area = static_cast<T>(radius * radius * 3.1415926535897932385L);
        }
    };
    
    
    //---------------------------------------------
    // Main
    int main()
    {
        // get area of square using template based function T=float
        std::unique_ptr<Geometry> geometry = std::make_unique<Square>();
        float areaSquare;
        geometry->getArea(areaSquare);
    
        // get area of circle using template based function T=long double
        geometry = std::make_unique<Circle>();
        long double areaCircle;
        geometry->getArea(areaCircle);
    
        std::cout << std::setprecision(20) << "Square area is " << areaSquare << ", Circle area is " << areaCircle << std::endl;
        return 0;
    }
    

    Output:

    Square area is 1, Circle area is 3.1415926535897932385

    Try it here

    0 讨论(0)
  • 2020-11-22 04:02

    There is a workaround for 'virtual template method' if set of types for the template method is known in advance.

    To show the idea, in the example below only two types are used (int and double).

    There, a 'virtual' template method (Base::Method) calls corresponding virtual method (one of Base::VMethod) which, in turn, calls template method implementation (Impl::TMethod).

    One only needs to implement template method TMethod in derived implementations (AImpl, BImpl) and use Derived<*Impl>.

    class Base
    {
    public:
        virtual ~Base()
        {
        }
    
        template <typename T>
        T Method(T t)
        {
            return VMethod(t);
        }
    
    private:
        virtual int VMethod(int t) = 0;
        virtual double VMethod(double t) = 0;
    };
    
    template <class Impl>
    class Derived : public Impl
    {
    public:
        template <class... TArgs>
        Derived(TArgs&&... args)
            : Impl(std::forward<TArgs>(args)...)
        {
        }
    
    private:
        int VMethod(int t) final
        {
            return Impl::TMethod(t);
        }
    
        double VMethod(double t) final
        {
            return Impl::TMethod(t);
        }
    };
    
    class AImpl : public Base
    {
    protected:
        AImpl(int p)
            : i(p)
        {
        }
    
        template <typename T>
        T TMethod(T t)
        {
            return t - i;
        }
    
    private:
        int i;
    };
    
    using A = Derived<AImpl>;
    
    class BImpl : public Base
    {
    protected:
        BImpl(int p)
            : i(p)
        {
        }
    
        template <typename T>
        T TMethod(T t)
        {
            return t + i;
        }
    
    private:
        int i;
    };
    
    using B = Derived<BImpl>;
    
    int main(int argc, const char* argv[])
    {
        A a(1);
        B b(1);
        Base* base = nullptr;
    
        base = &a;
        std::cout << base->Method(1) << std::endl;
        std::cout << base->Method(2.0) << std::endl;
    
        base = &b;
        std::cout << base->Method(1) << std::endl;
        std::cout << base->Method(2.0) << std::endl;
    }
    

    Output:

    0
    1
    2
    3
    

    NB: Base::Method is actually surplus for real code (VMethod can be made public and used directly). I added it so it looks like as an actual 'virtual' template method.

    0 讨论(0)
  • 2020-11-22 04:03

    At least with gcc 5.4 virtual functions could be template members but has to be templates themselves.

    #include <iostream>
    #include <string>
    class first {
    protected:
        virtual std::string  a1() { return "a1"; }
        virtual std::string  mixt() { return a1(); }
    };
    
    class last {
    protected:
        virtual std::string a2() { return "a2"; }
    };
    
    template<class T>  class mix: first , T {
        public:
        virtual std::string mixt() override;
    };
    
    template<class T> std::string mix<T>::mixt() {
       return a1()+" before "+T::a2();
    }
    
    class mix2: public mix<last>  {
        virtual std::string a1() override { return "mix"; }
    };
    
    int main() {
        std::cout << mix2().mixt();
        return 0;
    }
    

    Outputs

    mix before a2
    Process finished with exit code 0
    
    0 讨论(0)
  • 2020-11-22 04:07

    Virtual Function Tables

    Let's begin with some background on virtual function tables and how they work (source):

    [20.3] What's the difference between how virtual and non-virtual member functions are called?

    Non-virtual member functions are resolved statically. That is, the member function is selected statically (at compile-time) based on the type of the pointer (or reference) to the object.

    In contrast, virtual member functions are resolved dynamically (at run-time). That is, the member function is selected dynamically (at run-time) based on the type of the object, not the type of the pointer/reference to that object. This is called "dynamic binding." Most compilers use some variant of the following technique: if the object has one or more virtual functions, the compiler puts a hidden pointer in the object called a "virtual-pointer" or "v-pointer." This v-pointer points to a global table called the "virtual-table" or "v-table."

    The compiler creates a v-table for each class that has at least one virtual function. For example, if class Circle has virtual functions for draw() and move() and resize(), there would be exactly one v-table associated with class Circle, even if there were a gazillion Circle objects, and the v-pointer of each of those Circle objects would point to the Circle v-table. The v-table itself has pointers to each of the virtual functions in the class. For example, the Circle v-table would have three pointers: a pointer to Circle::draw(), a pointer to Circle::move(), and a pointer to Circle::resize().

    During a dispatch of a virtual function, the run-time system follows the object's v-pointer to the class's v-table, then follows the appropriate slot in the v-table to the method code.

    The space-cost overhead of the above technique is nominal: an extra pointer per object (but only for objects that will need to do dynamic binding), plus an extra pointer per method (but only for virtual methods). The time-cost overhead is also fairly nominal: compared to a normal function call, a virtual function call requires two extra fetches (one to get the value of the v-pointer, a second to get the address of the method). None of this runtime activity happens with non-virtual functions, since the compiler resolves non-virtual functions exclusively at compile-time based on the type of the pointer.


    My problem, or how I came here

    I'm attempting to use something like this now for a cubefile base class with templated optimized load functions which will be implemented differently for different types of cubes (some stored by pixel, some by image, etc).

    Some code:

    virtual void  LoadCube(UtpBipCube<float> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
    virtual void  LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
    virtual void  LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
    

    What I'd like it to be, but it won't compile due to a virtual templated combo:

    template<class T>
        virtual void  LoadCube(UtpBipCube<T> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
                long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
    

    I ended up moving the template declaration to the class level. This solution would have forced programs to know about specific types of data they would read before they read them, which is unacceptable.

    Solution

    warning, this isn't very pretty but it allowed me to remove repetitive execution code

    1) in the base class

    virtual void  LoadCube(UtpBipCube<float> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
                long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
    virtual void  LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
                long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
    virtual void  LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
                long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
    

    2) and in the child classes

    void  LoadCube(UtpBipCube<float> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
    { LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }
    
    void  LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
    { LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }
    
    void  LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
    { LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }
    
    template<class T>
    void  LoadAnyCube(UtpBipCube<T> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1);
    

    Note that LoadAnyCube is not declared in the base class.


    Here's another stack overflow answer with a work around: need a virtual template member workaround.

    0 讨论(0)
  • 2020-11-22 04:07

    To answer the second part of the question:

    If they can be virtual, what is an example of a scenario in which one would use such a function?

    This is not an unreasonable thing to want to do. For instance, Java (where every method is virtual) has no problems with generic methods.

    One example in C++ of wanting a virtual function template is a member function that accepts a generic iterator. Or a member function that accepts a generic function object.

    The solution to this problem is to use type erasure with boost::any_range and boost::function, which will allow you to accept a generic iterator or functor without the need to make your function a template.

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