我听说c++的类成员函数模板不能是虚的。这是真的吗?

如果它们可以是虚拟的,那么有什么场景可以使用这样的函数呢?


当前回答

我目前的解决方案如下(禁用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
        );
    }
}

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

其他回答

C++ doesn't allow virtual template member functions right now. The most likely reason is the complexity of implementing it. Rajendra gives good reason why it can't be done right now but it could be possible with reasonable changes of the standard. Especially working out how many instantiations of a templated function actually exist and building up the vtable seems difficult if you consider the place of the virtual function call. Standards people just have a lot of other things to do right now and C++1x is a lot of work for the compiler writers as well.

什么时候需要模板成员函数?我曾经遇到过这样的情况,我试图用纯虚拟基类重构一个层次结构。这是一种执行不同策略的糟糕风格。我想将其中一个虚函数的实参更改为数值类型,而不是重载成员函数并覆盖所有子类中的每一个重载,我尝试使用虚模板函数(并且不得不发现它们不存在)。

不,他们不能。但是:

template<typename T>
class Foo {
public:
  template<typename P>
  void f(const P& p) {
    ((T*)this)->f<P>(p);
  }
};

class Bar : public Foo<Bar> {
public:
  template<typename P>
  void f(const P& p) {
    std::cout << p << std::endl;
  }
};

int main() {
  Bar bar;

  Bar *pbar = &bar;
  pbar -> f(1);

  Foo<Bar> *pfoo = &bar;
  pfoo -> f(1);
};

如果您想要做的只是拥有一个公共接口并将实现推迟到子类,则效果大致相同。

从c++模板的完整指南:

Member function templates cannot be declared virtual. This constraint is imposed because the usual implementation of the virtual function call mechanism uses a fixed-size table with one entry per virtual function. However, the number of instantiations of a member function template is not fixed until the entire program has been translated. Hence, supporting virtual member function templates would require support for a whole new kind of mechanism in C++ compilers and linkers. In contrast, the ordinary members of class templates can be virtual because their number is fixed when a class is instantiated

如果预先知道模板方法的类型集,则'虚拟模板方法'有一个变通方法。

为了说明这个想法,在下面的例子中只使用了两种类型(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可以被设为public并直接使用)。 我添加它,使它看起来像一个实际的“虚拟”模板方法。

我看了所有的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
}

我希望你们能明白我的意思。你不能使用虚拟模板函数,因为就编译器而言,它们是两个完全不同的函数;作为隐式参数,此参数具有不同的类型。

虚拟模板不能工作的另一个原因同样有效。因为虚表是快速实现虚函数的最佳方式。