一个类成员函数模板可以是虚函数吗?

386
我听说C++类成员函数模板不能是虚函数。这是真的吗?
如果它们可以是虚函数,那么有什么情况下会使用这样的函数的例子呢?

15
我曾遇到类似的问题,也了解到同时使用虚函数和模板是具有争议的。我的解决方案是编写模板魔术代码,这些代码将在派生类中共用,并调用一个纯虚函数来执行特定的操作。当然,这与我的问题本质相关,可能不适用于每个情况。 - Tamás Szelei
15个回答

2
如果模板方法的类型集在预先已知,那么“虚拟模板方法”有一个解决方法。
为了说明这个想法,在下面的示例中只使用了两种类型(int和double)。
在那里,“虚拟”模板方法(Base::Method)调用相应的虚拟方法(Base::VMethod之一),然后调用模板方法实现(Impl::TMethod)。
只需要在派生实现(AImpl、BImpl)中实现模板方法TMethod并使用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;
}

输出:

0
1
2
3

注意: Base::Method对于实际代码来说是多余的(VMethod可以被公开并直接使用)。 我添加了它,以使其看起来像一个真正的“虚拟”模板方法。

我在解决工作中的一个问题时想出了这个解决方案。它似乎与上面Mark Essel的解决方案相似,但我希望它实现和解释得更好。 - sad1raf
我已经将这称为代码混淆,而且你仍然无法避免每次需要调用一个与已实现的类型不兼容的模板函数时都必须修改原始的“Base”类的事实。避免这种必要性正是模板的意图... - Aconcagua
Essel的方法完全不同:普通虚函数可以接受不同的模板实例化,而派生类中的最终模板函数仅用于避免代码重复,甚至在基类中也没有对应物... - Aconcagua

0

至少在gcc 5.4中,虚函数可以是模板成员,但必须本身就是模板。

#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;
}

输出

mix before a2
Process finished with exit code 0

0

我已经查看了所有14个答案,有些人给出了虚拟模板函数无法工作的原因,而其他人则展示了一种解决方法。甚至有一个答案表明虚拟类可以有虚拟函数,这并不令人惊讶。

我的答案将直接说明标准为什么不允许虚拟模板函数。因为很多人一直在抱怨。首先,我简直不敢相信有些人评论说虚拟函数可以在编译时推导出来。那是我听过的最愚蠢的事情。

无论如何,我确信标准规定对象的this指针是其成员函数的第一个参数。

struct MyClass
{
 void myFunction();
}

// translate to
void myFunction(MyClass*);

现在我们已经清楚了这一点。然后我们需要知道模板的转换规则。模板参数非常有限,可以隐式转换为什么。我不记得所有的内容,但你可以查看完整的参考资料C++ Primer。例如,T* 可以转换为 const T*。数组可以转换为指针。然而,派生类作为模板参数不能转换为基类。
struct A {};
struct B : A {};

template<class T>
void myFunction(T&);

template<>
void myFunction<A>(A&) {}

int main()
{
 A a;
 B b;

 myFunction(a); //compiles perfectly
 myFunction((A&)b); // compiles nicely
 myFunction(b); //compiler error, use of undefined template function
}

所以我希望你能理解我的意思。你不能有一个虚拟模板函数,因为在编译器看来它们是两个完全不同的函数;因为它们的隐式 this 参数是不同类型的。

另一个原因为什么虚拟模板无法工作同样有效。由于虚拟表是实现快速虚拟函数的最佳方式。


那么...既然隐式的this参数是隐式的,为什么它应该成为一个模板参数呢?它可以被排除在整个混乱之外,然后解析的函数将像以前一样对其进行任何操作(不使用模板)。因此,对我来说,这与问题无关。 - Sz.

0

我的当前解决方案是以下内容(已禁用RTTI - 也可以使用std :: type_index):

#include <type_traits>
#include <iostream>
#include <tuple>

class Type
{
};

template<typename T>
class TypeImpl : public Type
{

};

template<typename T>
inline Type* typeOf() {
    static Type* typePtr = new TypeImpl<T>();
    return typePtr;
}

/* ------------- */

template<
    typename Calling
    , typename Result = void
    , typename From
    , typename Action
>
inline Result DoComplexDispatch(From* from, Action&& action);

template<typename Cls>
class ChildClasses
{
public:
    using type = std::tuple<>;
};

template<typename... Childs>
class ChildClassesHelper
{
public:
    using type = std::tuple<Childs...>;
};

//--------------------------

class A;
class B;
class C;
class D;

template<>
class ChildClasses<A> : public ChildClassesHelper<B, C, D> {};

template<>
class ChildClasses<B> : public ChildClassesHelper<C, D> {};

template<>
class ChildClasses<C> : public ChildClassesHelper<D> {};

//-------------------------------------------

class A
{
public:
    virtual Type* GetType()
    {
        return typeOf<A>();
    }

    template<
        typename T,
        bool checkType = true
    >
        /*virtual*/void DoVirtualGeneric()
    {
        if constexpr (checkType)
        {
            return DoComplexDispatch<A>(this, [&](auto* other) -> decltype(auto)
                {
                    return other->template DoVirtualGeneric<T, false>();
                });
        }
        std::cout << "A";
    }
};

class B : public A
{
public:
    virtual Type* GetType()
    {
        return typeOf<B>();
    }
    template<
        typename T,
        bool checkType = true
    >
    /*virtual*/void DoVirtualGeneric() /*override*/
    {
        if constexpr (checkType)
        {
            return DoComplexDispatch<B>(this, [&](auto* other) -> decltype(auto)
                {
                    other->template DoVirtualGeneric<T, false>();
                });
        }
        std::cout << "B";
    }
};

class C : public B
{
public:
    virtual Type* GetType() {
        return typeOf<C>();
    }

    template<
        typename T,
        bool checkType = true
    >
    /*virtual*/void DoVirtualGeneric() /*override*/
    {
        if constexpr (checkType)
        {
            return DoComplexDispatch<C>(this, [&](auto* other) -> decltype(auto)
                {
                    other->template DoVirtualGeneric<T, false>();
                });
        }
        std::cout << "C";
    }
};

class D : public C
{
public:
    virtual Type* GetType() {
        return typeOf<D>();
    }
};

int main()
{
    A* a = new A();
    a->DoVirtualGeneric<int>();
}

// --------------------------

template<typename Tuple>
class RestTuple {};

template<
    template<typename...> typename Tuple,
    typename First,
    typename... Rest
>
class RestTuple<Tuple<First, Rest...>> {
public:
    using type = Tuple<Rest...>;
};

// -------------
template<
    typename CandidatesTuple
    , typename Result
    , typename From
    , typename Action
>
inline constexpr Result DoComplexDispatchInternal(From* from, Action&& action, Type* fromType)
{
    using FirstCandidate = std::tuple_element_t<0, CandidatesTuple>;

    if constexpr (std::tuple_size_v<CandidatesTuple> == 1)
    {
        return action(static_cast<FirstCandidate*>(from));
    }
    else {
        if (fromType == typeOf<FirstCandidate>())
        {
            return action(static_cast<FirstCandidate*>(from));
        }
        else {
            return DoComplexDispatchInternal<typename RestTuple<CandidatesTuple>::type, Result>(
                from, action, fromType
            );
        }
    }
}

template<
    typename Calling
    , typename Result
    , typename From
    , typename Action
>
inline Result DoComplexDispatch(From* from, Action&& action)
{
    using ChildsOfCalling = typename ChildClasses<Calling>::type;
    if constexpr (std::tuple_size_v<ChildsOfCalling> == 0)
    {
        return action(static_cast<Calling*>(from));
    }
    else {
        auto fromType = from->GetType();
        using Candidates = decltype(std::tuple_cat(std::declval<std::tuple<Calling>>(), std::declval<ChildsOfCalling>()));
        return DoComplexDispatchInternal<Candidates, Result>(
            from, std::forward<Action>(action), fromType
        );
    }
}

我唯一不喜欢的是你必须要定义/注册所有的子类。


-1

虚函数的调用方式是怎样的?

Vtable会为类的每个虚函数包含一个条目,在运行时它会选择特定函数的地址并调用相应的函数。

在使用函数模板时,如何调用正确的虚函数?

在使用函数模板时,用户可以使用任何类型来调用此函数。同一个函数基于不同的类型有多个版本。现在,在这种情况下,由于不同版本的存在,必须维护vtable中的许多条目以便调用同一个函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接