Class 模板派生自另一个 Class 模板类型推导

Class Template Derived from Another Class Template Type Deduction

我正在尝试编写仿函数记忆器以节省重复昂贵的函数调用的时间。在我的 class 设计中,我正在努力寻找一个简单的界面。

使用这个 Functor 基数 class:

template <typename TOut, typename TIn>
class Functor {
public:
    virtual
    ~Functor() {
    }

    virtual
    TOut operator()(TIn input) = 0;
};

我现在想写一个 class 来封装和记忆一个仿函数。除了封装一个 FunctorMemoizedFunctor 本身就是一个 Functor。这导致有 3 个模板参数。

这是一个工作示例:

#include <unordered_map>

template <typename F, typename TOut, typename TIn>
class MemoizedFunctor : public Functor<TOut, TIn> {
public:
    MemoizedFunctor(F f) : f_(f) {
    }

    virtual
    ~MemoizedFunctor() {
    }

    virtual
    TOut operator()(TIn input) override {
        if (cache_.count(input)) {
            return cache_.at(input);
        } else {
            TOut output = f_(input);
            cache_.insert({input, output});
            return output;
        }
    }

private:
    F f_;
    std::unordered_map<TIn, TOut> cache_;
};

class YEqualsX : public Functor<double, double> {
public:
    virtual
    ~YEqualsX() {
    }

    double operator()(double x) override {
        return x;
    }
};

int main() {
    MemoizedFunctor<YEqualsX, double, double> f((YEqualsX())); // MVP

    f(0); // First call
    f(0); // Cached call

    return 0;
}

我觉得必须有一种方法可以避免指定所有 3 个模板参数。给定传递给 MemoizedFunctor 的构造函数的函数,我认为可以推导出所有三个模板参数。

我不确定如何重写 class 以便使用它不需要所有模板规范。

我尝试使用指向 Functor 的智能指针作为 MemoizedFunctor 中的成员变量。这消除了第一个模板参数,但现在 class 的用户必须将智能指针传递给 MemoizedFunctor class.

总而言之,我希望 MemoizedFunctor 的所有模板参数在构造时自动推导。我相信这是可能的,因为在构建时所有模板参数都是明确的。

In summary, I would like to have all the template arguments of MemoizedFunctor be automatically be deduced on construction. I believe this is possible because on construction all template arguments are unambiguous.

如果我理解正确,MemoizedFunctor 的第一个模板类型永远是 Functor<TOut, TIn>,或者是从某些 Functior<TOut, TIn> 继承的东西,其中 TOutTInMemoizedFunctor.

的第二个和第三个模板参数

看来你是在找推导指南。

为了推导出第二个和第三个模板参数,我建议声明(不需要定义,因为仅在 decltype() 内部使用)以下几个函数

template <typename TOut, typename TIn>
constexpr TIn getIn (Functor<TOut, TIn> const &);

template <typename TOut, typename TIn>
constexpr TOut getOut (Functor<TOut, TIn> const &);

现在,使用decltype()std::declval(),用户定义的推导指南就变成了

template <typename F>
MemoizedFunctor(F)
   -> MemoizedFunctor<F,
                      decltype(getOut(std::declval<F>())),
                      decltype(getIn(std::declval<F>()))>;

下面是一个完整的编译示例

#include <unordered_map>

template <typename TOut, typename Tin>
class Functor
 {
   public:
    virtual ~Functor ()
     { }

    virtual TOut operator() (Tin input) = 0;
 };

template <typename TOut, typename TIn>
constexpr TIn getIn (Functor<TOut, TIn> const &);

template <typename TOut, typename TIn>
constexpr TOut getOut (Functor<TOut, TIn> const &);

template <typename F, typename TOut, typename TIn>
class MemoizedFunctor : public Functor<TOut, TIn>
 {
   public:
      MemoizedFunctor(F f) : f_{f}
       { }

      virtual ~MemoizedFunctor ()
       { }

      virtual TOut operator() (TIn input) override
       {
         if ( cache_.count(input) )
            return cache_.at(input);
         else
          {
            TOut output = f_(input);
            cache_.insert({input, output});
            return output;
          }
       }

   private:
      F f_;
      std::unordered_map<TIn, TOut> cache_;
 };

class YEqualsX : public Functor<double, double>
 {
   public:
      virtual ~YEqualsX ()
       { }

      double operator() (double x) override
       { return x; }
 };

template <typename F>
MemoizedFunctor(F)
   -> MemoizedFunctor<F,
                      decltype(getOut(std::declval<F>())),
                      decltype(getIn(std::declval<F>()))>;

int main ()
 {
   MemoizedFunctor f{YEqualsX{}};

   f(0); // First call
   f(0); // Cached call
 }

-- 编辑 --

Aschepler 在评论中指出,此解决方案可能存在一个缺点:无法从函数 return 编辑某些类型。

例如,函数不能 return 一个 C-style 数组。

推导 TOut(由 operator() 编辑的类型 return)这不是一个问题,因为 return 类型是由方法编辑的 return 所以 return也可以通过 getOut().

但这可能(一般来说)是 TIn 的问题:如果 TIn 例如 int[4](在这种情况下不可能,因为是用作无序映射的键,但我重复一遍,一般来说),int[4] 不能 return 由 getIn() 编辑。

您可以绕过这个问题 (1) 添加类型包装器结构,如下所示

template <typename T>
struct typeWrapper
 { using type = T; };

(2) 将 getIn() 修改为 return 包装器 TIn

template <typename TOut, typename TIn>
constexpr typeWrapper<TIn> getIn (Functor<TOut, TIn> const &);

和(3)修改推导指南以从包装器

中提取TIn
template <typename F>
MemoizedFunctor(F)
   -> MemoizedFunctor<F,
                      decltype(getOut(std::declval<F>())),
                      typename decltype(getIn(std::declval<F>()))::type>;

在:

template <typename F, typename TOut, typename TIn>
class MemoizedFunctor : public Functor<TOut, TIn> { ... }

如果 F 必须 Functor 的实现(我猜是这种情况?),您可以添加一些别名Functor 让你的生活更轻松(这些可以在外部而不是内部完成,但让这种侵入似乎是合理的):

template <typename TOut, typename TIn>
class Functor {
public:
    using in_param = TIn;
    using out_param = TOut;
    // ... rest as before ...
};

然后更改 MemoizedFunctor 以直接使用这些别名。你真的没有独立的模板参数,它们是完全依赖的吧?

template <typename F>
class MemoizedFunctor : public Functor<typename F::out_param, typename F::in_param> { ... }

有了这个改变(同样改变了你对 TOutTIn 的内部使用),这就如愿以偿了(因为,当然,我们现在只有一个模板参数,所以只有一个提供):

MemoizedFunctor<YEqualsX> f(YEqualsX{});

并且该参数可以直接通过 CTAD 推导出来,无需任何进一步更改

MemoizedFunctor f(YEqualsX{});