从另一个类模板类型推导中得出的类模板

时间:2019-05-23 19:09:23

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

我正在尝试编写函子备忘录,以节省重复执行昂贵函数的时间。在我的课堂设计中,我努力寻找一个简单的界面。

使用此Functor基类:

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

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

我现在想编写一个将封装和记忆函子的类。除了封装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的构造函数后,我认为可以推导出所有三个模板参数。

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

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

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

2 个答案:

答案 0 :(得分:4)

  

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

如果我理解正确,MemoizedFunctor的第一个模板类型就是Functor<TOut, TIn>,或者是从某些Functior<TOut, TIn>继承的东西,其中TOut和{{1} }是TIn的第二和第三模板参数。

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

为了推断第二个和第三个模板参数,我建议声明以下几对函数(无需定义,因为仅在MemoizedFunctor中使用)

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>()))>;

-编辑-

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

通过示例,函数无法返回C样式的数组。

确切地推论#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 } (由TOut返回的类型)并不是一个问题,因为它是方法返回的类型,因此operator()也可以返回。

但这通常是getOut()的问题:例如,如果TInTIn(在这种情况下就不能用,因为一个无序映射的键,但是,一般来说,我要重复),int[4]不能返回int[4]

您可以解决此问题(1)如下添加类型包装器结构

getIn()

(2)修改template <typename T> struct typeWrapper { using type = T; }; 以返回包装器getIn()

TIn

和(3)修改推导指南以从包装器中提取template <typename TOut, typename TIn> constexpr typeWrapper<TIn> getIn (Functor<TOut, TIn> const &);

TIn

答案 1 :(得分:3)

在:

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{});