【问题标题】:Class Template Derived from Another Class Template Type Deduction类模板派生自另一个类模板类型推导
【发布时间】:2019-05-23 19:09:23
【问题描述】:

我正在尝试编写一个仿函数记忆器来节省重复昂贵的函数调用的时间。在我的课堂设计中,我很难找到一个简单的界面。

使用这个 Functor 基类:

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

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

我现在想写一个类来封装和记忆一个仿函数。除了封装Functor,MemoizedFunctor 本身也是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 的构造函数的函数,我认为可以推导出所有三个模板参数。

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

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

总之,我想让MemoizedFunctor 的所有模板参数在构造时自动推导出来。我相信这是可能的,因为在构造时所有模板参数都是明确的。

【问题讨论】:

  • 您正在翻转输入/输出参数 - 您能选择一致的顺序吗?
  • @Barry 对此感到抱歉,希望现在已修复。从一台机器转移到这台机器时出错。

标签: c++ templates c++17 sfinae template-argument-deduction


【解决方案1】:

总之,我想让 MemoizedFunctor 的所有模板参数在构造时自动推导出来。我相信这是可能的,因为在构造时所有模板参数都是明确的。

如果我理解正确,MemoizedFunctor 的第一个模板类型曾经是 Functor&lt;TOut, TIn&gt;,或者从某些 Functior&lt;TOut, TIn&gt; 继承的东西,其中 TOut 和 TIn 是 @987654326 的第二个和第三个模板参数@。

在我看来,您正在寻找扣除指南。

为了推导出第二个和第三个模板参数,我建议声明(不需要定义,因为只在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 在评论中指出,此解决方案可能存在一个缺陷:某些类型无法从函数返回。

例如,函数不能返回 C 样式的数组。

这在推断TOut(operator() 返回的类型)不是问题,因为它是方法返回的类型,所以getOut() 也可以返回。

但这可能是(一般来说)TIn 的问题:如果 TIn 是,例如,int[4](在这种情况下不能是,因为它被用作无序映射的键但是,我再说一遍,一般来说),getIn() 不能返回 int[4]。

你可以绕过这个问题(1)添加一个类型包装器结构如下

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

(2) 修改getIn() 以返回包装器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>;

【讨论】:

  • 非常感谢您的回答!这看起来完全符合我的期望。明天我会仔细研究一下,试图理解这一切。
  • 这种函数声明方法的一个缺点通常是某些 C++ 类型(原始数组、抽象类、函数类型)永远不可能是函数返回类型。对于这个例子来说不是一个真正的问题,因为Functor&lt;TOut, TIn&gt;::operator() 已经将它们用作返回类型和参数类型。
  • @aschepler - 好点; TOut 肯定不是问题,因为 operator() 的返回类型是可返回的;但可能是TIn 的问题:如果TIn 是(例如)int[4],operator() 可以接收它但getIn() 不能返回它。嗯...也许我会添加一个警告。
  • @aschepler - 稍微考虑一下这个问题,有一个简单的解决方案:返回 TIn 类型,来自 getIn(),包装在一个结构中。感谢您指出问题。
  • @Pavis11 - aschepler 指出了我的解决方案中可能存在的问题;修改答案以绕过它(但也请看一下 Barry 解决方案:如果您可以在 Functor 基类中添加几个 using 别名,一切都会变得简单得多)。
【解决方案2】:

在:

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> { ... }

随着这一改变(以及同样改变您对TOut 和TIn 的内部使用),这将按需要工作(当然,因为我们现在只有一个模板参数,所以只提供一个):

MemoizedFunctor<YEqualsX> f(YEqualsX{});

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

MemoizedFunctor f(YEqualsX{});

【讨论】:

  • 您认为F 也必须是Functor 的实现的假设是正确的。我在想必须存在与此类似的解决方案,因为正如您所说,我实际上并没有 3 个独立的模板参数。我实际上尝试了这个没有别名的解决方案 (class MemoizedFunctor : public Functor&lt;typename F::TOut, typename F::TIn&gt;),但当然这不起作用。我知道这种语法很时髦,看起来这些别名就是解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-04
  • 2016-02-17
  • 2015-04-10
  • 1970-01-01
  • 2015-08-25
相关资源
最近更新 更多