Avoid switching on template parameters

前端 未结 3 920
情书的邮戳
情书的邮戳 2021-01-24 18:02

Simplified I have the following class hierarchy:

class BaseVec {
  public:
    BaseVec() {};
    virtual ~BaseVec() {};

    virtual double get_double(int i) con         


        
相关标签:
3条回答
  • 2021-01-24 18:45

    Why not change foo_template to be:

    template<typename T>
    double foo_template(Vec<T>*) {
      // use v which involves calling get
      return result;
    }
    

    and foo to be:

    template<typename T>
    double foo (Vec<T>* v )
    {
    return foo_template(v)
    }
    

    and let argument deduction do the work?

    (You can probably get rid of one of the functions, but I wanted to keep is as close to the original)

    0 讨论(0)
  • 2021-01-24 18:54

    Automatic dispatch in C++ happens with runtime-polymorphism by means of virtua function and with static_type polymorphism by mnas of a static_Cast, but you need to know what type to cast.

    With a different design, avoiding void*, you can do the following:

    template<class Derived>
    class static_polimorphic {};
    
    template<class A>
    A& upcast(static_polymorphic<A>& sa)
    { return static_cast<A&>(sa); }
    
    template<class A>
    const A& upcast(const static_polymorphic<A>& sa)
    { return static_cast<const A&>(sa); }
    

    Now, you classes shold be like

    class C1: public static_polymorphic<C1>
    {
     ....
    };
    
    class C2: public static_polymorphic<C2>
    {
     ....
    };
    

    Polymorphism will then apply as

    template<class A> 
    void function(const static_polymorphic<A>& sa)
    {
       A& a = upcast(sa);
       a.methods();
       ...
    }
    

    In other words, the type is anymore represented a base member variable, but by a base template parameter.

    Note also that being the bases differentiated by the derived type, common functions will not reaqire to be virtual. You can so completely avoid runtimes-based polymorphism, unless you have to store different runtime-type created object into a same container or collection.

    For that purpose you can use a second non-tempetised base with abstract virtual function as "launchers" for the one in the derived classes. (May be better to use the runtime polymorphic one as first base, to simplify run-time pointer convertion, since there will be no offset)

    0 讨论(0)
  • 2021-01-24 18:59

    First of all, don't use reinterpret_cast while converting to/from pointer to polymorphic class. You can write a simple pointer wrapper which allow you to use safe casting operator static_cast:

    template <class Type>
    class PointerWrapper
    {
    public:
    
        PointerWrapper(Type* object);
        PointerWrapper& operator=(Type* object);
        Type* operator->();
    
    protected:
    
        Type* object;
    
    };
    
    template <class Type>
    PointerWrapper<Type>::PointerWrapper(Type* object) :
        object(object)
    {
    }
    
    template <class Type>
    PointerWrapper<Type>& PointerWrapper<Type>::operator=(Type* object)
    {
        this->object = object;
    }
    
    template <class Type>
    Type* PointerWrapper<Type>::operator->()
    {
        return object;
    }
    

    Now you can write:

    typedef PointerWrapper<BaseVec> BaseVecPointer;
    
    template<typename T>
    double foo(void* p) {
        BaseVecPointer* vp = static_cast<BaseVecPointer*>(p);
        // ...
        // ... = (*vp)->get_double(...);
        // ...
        return result;
    }
    

    In this code polymorphism capabilities were used, i.e. function get_double was called instead of calling get.

    But if you want to call just get, not get_double, i.e. you want to call template functions with different template arguments depending on the value of run-time variable, you can use the following method:

    enum FooTypes
    {
        NoFooType = -1,
        DoubleFooType = 0,
        IntegerFooType = 1,
        // ...
        FooTypesCount
    };
    
    template<FooTypes fooType>
    struct ChooseType
    {
        static
        const FooTypes value = NoFooType;
    
        typedef void Type;
    };
    
    template<>
    struct ChooseType<DoubleFooType>
    {
        static
        const FooTypes value = DoubleFooType;
    
        typedef double Type;
    };
    
    template<>
    struct ChooseType<IntegerFooType>
    {
        static
        const FooTypes value = IntegerFooType;
    
        typedef int Type;
    };
    

    Here you should write specializations of the class template ChooseType for all possible values of type variable. Following code describes the function ChooseFoo which selects what specialization of foo_template function template should be called:

    typedef double (*FooFunction)(void*);
    
    template<FooTypes fooType>
    FooFunction ChooseFooImpl(int type)
    {
        if (type == fooType)
        {
            if (ChooseType<fooType>::value != NoFooType)
            {
                return foo_template<typename ChooseType<fooType>::Type>;
            }
            else
            {
                return NULL;
            }
        }
        else
        {
            return ChooseFooImpl<(FooTypes)(fooType - 1)>(type);
        }
    }
    
    template<>
    FooFunction ChooseFooImpl<NoFooType>(int type)
    {
        return NULL;
    }
    
    FooFunction ChooseFoo(int type)
    {
        return ChooseFooImpl<FooTypesCount>(type);
    }
    

    And this is foo function implementation:

    double foo(void* p, int type)
    {
        FooFunction fooFunction = ChooseFoo(type);
    
        if (fooFunction != NULL)
        {
            return fooFunction(p);
        }
        else
        {
            //unsupported type
            // ...
        }
    }
    
    0 讨论(0)
提交回复
热议问题