如何将模板参数限制为模板化接口特化的后代?

How to limit template parameters to a descendent that is a specialization of a templated interface?

假设以下情况:

  1. 有一个模板化接口定义了一组对不同数据类型的操作。
  2. 此接口由定义实际数据类型操作的各种专用 classes 实现。
  3. 有一些管理 class 必须处理 2.
  4. 中定义的 classes 的实例

简化的示例代码可能如下所示:

#include <iostream>
#include <type_traits>

template <typename R, typename S>
class ICanDoIt
{
    public:
        virtual void doStuff() = 0;

    protected:
        ICanDoIt<R, S>(R rA, S sA) : r(rA), s(sA) {};
        R r;
        S s;
};

class DoesIt : public ICanDoIt<int, double>
{
        public:
            DoesIt(int iA, double dA) : ICanDoIt(iA, dA) {};

            virtual void doStuff()
                { std::cout << "r * s = " << r * s << " done." << std::endl; }
};

template <typename T>
class NeedsSomeoneWhoCanDoIt
{
    static_assert(std::is_base_of<ICanDoIt<R, S>, T>::value, 
                  "T needs to be able to do it.");

    public:
        NeedsSomeoneWhoCanDoIt(const T& doesItA) : doesIt(doesItA) {};
        void getItDone() { doesIt.doStuff(); };

    private:
        T doesIt;
};


int main()
{
    DoesIt doesIt(5, 2.2);
    NeedsSomeoneWhoCanDoIt<DoesIt> needsIt(doesIt);
    needsIt.getItDone();
}

如果取消模板化界面 "ICanDoIt" 代码将实际工作。但是模板化版本的 static_assert 将失败,因为 ICanDoIt 的模板参数被 DoesIt 的声明中执行的特化包装和隐藏。

如何将管理 classes (NeedsSomeoneWhoCanDoIt) 模板参数 "T" 限制为 ICanDoIt 的 any 专业化,无论选择了哪种类型对于 ICanDoIt 专业化期间的 R、S?

您始终可以使用于实例化 ICanDoItRS 的实际类型可供派生 class 访问,即

template <typename R, typename S> class ICanDoIt {
public:
  typedef R R_t;
  typedef S S_t;

  virtual void doStuff() = 0;
};

这样你的static_assert就会变成

static_assert(std::is_base_of<ICanDoIt<typename T::R_t, typename T::S_t>,
                              T>::value,
              "T needs to be able to do it.");

根据您的实际代码的外观,如果您定义一个纯粹的抽象基础 class(即实际类型 ICanDoItBase 而不是模板),您的设计可能会变得更加清晰将继承 ICanDoIt 中当前的模板化功能,这将再次成为 DoesIt 的基础。

NeedsSomeoneWhoCanDoIt 可以直接使用多态基础 class ICanDoItBase 而无需任何额外的类型检查。

您不需要发布模板参数。基于 SFINAE 的标准方法就可以正常工作。

namespace detail {
    template<class R, class S>
    std::true_type test(ICanDoIt<R, S>*);
    std::false_type test(...);
}

template<class T>
using can_do_it = decltype(detail::test((T*)nullptr));