首页 > 解决方案 > 消除 C++ 中的冗余模板参数

问题描述

我正在尝试编写一个fmap在 Haskell中实现的演示,continuation我的代码如下所示:

#include <cstdio>
#include <functional>

template <typename X>
using Callback = std::function<void(X)>;

template <typename X, typename Y>
using Fun = std::function<Y(X)>;

template <typename X, typename Y>
struct F_map;

template <typename X>
struct __F {
  virtual void operator()(Callback<X>&& callback) = 0;

  virtual __F<X>* self() { return this; }

  template <typename Y>
  auto map(Fun<X, Y>&& f) { return F_map(self(), f); }
};

template <typename X>
struct F_id : __F<X> {
  const X x;

  F_id(const X& x) : x(x) {}

  __F<X>* self() override { return this; }

  void operator()(Callback<X>&& callback) override { callback(x); }
};

template <typename X, typename Y>
struct F_map : __F<Y> {
  __F<X>* upstream;
  Fun<X, Y> f;

  F_map(__F<X>* upstream, const Fun<X, Y>& f) : upstream(upstream), f(f) {}

  __F<Y>* self() override { return this; }

  void operator()(Callback<Y>&& callback) override {
    upstream->operator()([=](X&& x) {
      callback(f(x));
    });
  }
};

int main(int argc, char* argv[]) {
  auto f =
    F_id(10)
      .map<int>([](int x) { return x + 2; })
      .map<const char*>([](int x) { return "1, 2, 3"; });
  f([](const char* x) { printf("%s\n", x); });
  return 0;
}

这很好用,但是map<int>andmap<const char*>看起来很难看。我认为这些声明可以省略,但是如果我删除它,我会收到一条错误消息,上面写着“没有函数模板的实例“F_id::map [with X=int]”与参数列表匹配”。

有什么想法可以删除这些模板参数吗?

标签: c++templatesfunctional-programming

解决方案


C++中有多种多态性。多态性是指代码中的单个变量具有不同的实现类型。

有经典的 C++ 继承和基于虚拟的多态性。有基于类型擦除的多态性。并且存在模板的静态多态性。

在许多意义上,这些多态性是相互对立的。如果你在应该使用另一个的时候使用一个,这就像在你应该使用逆变的时候使用协方差一样。你的代码可能会绊倒,但它只有在被迫时才会起作用,比如拿一个方形钉子、一个圆孔和一个大锤子。

您的<int>要求是使用错误类型的多态性的示例,并且<int>是锤子将其砸入错误形状的孔中。

您正在尝试使用

template <typename X>
using Callback = std::function<void(X)>;

template <typename X, typename Y>
using Fun = std::function<Y(X)>;

作为模式匹配器。它们不是模式匹配器,即使在特定情况下它们可以用作模式匹配器。 Callback并且Fun类型橡皮擦

Callback<X>接受任何可以用可以从 转换的东西来调用的东西X,并存储它。然后几乎忘记了关于它的所有其他事实(嗯,它记得如何复制它、它的 typeid 和一些其他随机事实)。

Fun<X,Y>接受可以用可以从 a 转换的东西调用的任何东西,X然后可以将其返回值转换为 a Y。然后它几乎忘记了关于它的所有其他事实。

这里:

template <typename Y>
auto map(Fun<X, Y>&& f) { return F_map(self(), f); }

你试图用它来表达“我接受一个f。请给我找一个Y匹配这个的f”。

这是模式匹配。类型擦除和模式匹配是相反的操作。

这是一个非常常见的错误。对于经典继承,它们有时最终会是同一件事。

std::function是为了忘记有关某事的信息,能够存储它,然后只使用您记得的部分。


第一个问题是,你需要模式匹配,还是这里需要一个类型函数?

可能你很擅长类型函数。

template <class F, class R = std::invoke_result_t<F, X>>
F_map<X,R> map(F&& f) { return {self(), std::forward<F>(f)}; }

这里我们将传入映射F到它的返回值R

您的代码还有其他问题。就像悬空指针一样。此外,它坚持知道可调用对象使用什么类型;在 C++ 中,你可以……只是不知道这一点。

因此,将 CRTP 用于静态多态性,并机械地忘记了我正在处理的类型,并用非类型擦除代码替换它们,我得到:

#include <cstdio>
#include <type_traits>

template <class Upstream, class F>
struct F_map;

template<class D>
struct mappable
{
  template <class F>
  F_map<D, F> map(F const& f) { return F_map(static_cast<D*>(this), f); }
};

template <class Upstream, class F>
struct F_map:
    mappable<F_map<Upstream, F>>
{
  Upstream* upstream;
  F f;

  F_map(Upstream* upstream, const F& f) : upstream(upstream), f(f) {}

  template<class Callback>
  void operator()(Callback&& callback) {
    (*upstream)([=](auto&& x) {
      callback(f(decltype(x)(x)));
    });
  }
};


template <typename X>
struct F_id:
    mappable<F_id<X>>
{
  const X x;

  F_id(const X& x) : x(x) {}

  template<class Callback>
  void operator()(Callback&& callback) { callback(x); }
};


int main(int argc, char* argv[]) {
  auto f =
    F_id(10)
      .map([](int x) { return x + 2; })
      .map([](int x) { return "1, 2, 3"; });
  f([](const char* x) { printf("%s\n", x); });
  return 0;
}

活生生的例子

我仍然认为您正在关注悬空指针,但我不确定。

的返回值map存储了一个指向我们调用它的对象的指针,并且该对象在我们创建时被临时销毁f

为了解决这个Upstream*问题,我会这样做:

template <class Upstream, class F>
struct F_map;

template<class D>
struct mappable
{
  template <class F>
  F_map<D, F> map(F const& f) const { return {*static_cast<D const*>(this), f}; }
};

template <class Upstream, class F>
struct F_map:
    mappable<F_map<Upstream, F>>
{
  Upstream upstream;
  F f;

  F_map(Upstream const& upstream, const F& f) : upstream(upstream), f(f) {}

  template<class Callback>
  void operator()(Callback&& callback) const {
    upstream([=](auto&& x) {
      callback(f(decltype(x)(x)));
    });
  }
};


template <typename X>
struct F_id:
    mappable<F_id<X>>
{
  const X x;

  F_id(const X& x) : x(x) {}

  template<class Callback>
  void operator()(Callback&& callback) const { callback(x); }
};

按值复制upstream


推荐阅读