【问题标题】:Templated Using with Nested Template使用嵌套模板进行模板化
【发布时间】:2018-08-24 04:27:14
【问题描述】:

以下代码不起作用,因为推断的模板参数 F 是 std::tuple,而我希望它是 Foo - 前者接受两个模板参数,后者接受一个。

#include <tuple>

template <typename T>
using Foo = std::tuple<int, T>;

template <template <typename> class F>
void foo(F<std::string> bar) {}

void test() {
  foo(Foo<std::string>());
}

有什么方法可以使类型推断与using 语句一起工作,而不是将Foo 变成它自己的类?

#include <tuple>

template <typename T>
class Foo {
  std::tuple<int, T> bar;
};

template <template <typename> class F>
void foo(F<std::string> bar) {}

void test() {
  foo(Foo<std::string>());
}

更多信息

我正在使用 C++17 的 std::variant 以及对单一类型通用的别名类型,我更愿意使用 using 语句声明这些,而不是为每个类型创建包装类。像这样的:

// Assuming Plus, Minus, etc all exist
template <typename T>
using Operation = std::variant<Plus<T>, Minus<T>, Times<T>>;

构建 Haskell 风格的仿函数

本练习的重点是基于 Haskell 的仿函数类型类松散地构建一个小型仿函数库。我已经定义了这样的“类型类”:

template <template <typename> class F>
class Functor {
public:
  template <typename T, typename U>
  static F<U> fmap(std::function<U(T)> f, F<T> functor);
};

但我还想添加一些糖,以便您可以创建一个通用映射器,该映射器将一个函数映射到任何函数类型,而无需预先指定函子类型:

template <typename T, typename U>
struct FMap {
  FMap(std::function<U(T)> f) : f_(f) {}

  template <template <typename> class F>
  F<U> operator()(F<T> functor) {
    return Functor<F>::fmap(f_, functor);
  }

private:
  std::function<U(T)> f_;
};

template <typename T, typename U>
FMap<T, U> fmap(std::function<U(T)> f) {
  return FMap<T, U>(f);
}

这适用于简单的值包装函子:

template <typename T>
class Value {
public:
  Value(T value) : value_(value) {}

  const T& value() const {
    return value_;
  }

private:
  T value_;
};

template <>
template <typename T, typename U>
Value<U> Functor<Value>::fmap(std::function<U(T)> f, Value<T> value) {
  return Value<U>(f(value.value()));
}

void test() {
  std::function<std::string(int)> fn = [](int x) {
    return std::to_string(x);
  };
  auto result = fmap(fn)(Value(42));
  // result.value() == "42"
}

现在我正试图让它与使用 std::tuplestd::variant 的更复杂的类型一起工作,就像上面的例子一样。

template <>
template <typename T, typename U>
Foo<U> Functor<Foo>::fmap(std::function<U(T)> f, Foo<T> value) {
  return Foo<U>(std::get<0>(value), f(std::get<1>(value)));
}

void test() {
  std::function<std::string(int)> fn = [](int x) {
    return std::to_string(x);
  };
  // This is the desirable syntax but it doesn't build
  // fmap(fn)(Foo<int>(42, 7));

  // This builds but it's super ugly
  fmap(fn).operator()<Foo>(Foo<int>(42, 7));
}

根据下面 SkepticalEmpiricist 的回复,我认为类型别名可能不是这里的方法,相反我将不得不引入小型包装器类 - 除非有 SFINAE 方法可以实现这一点。

这个库主要是一种好奇心,也是我探索一些更高级模板概念的一种方式——感谢您的帮助!

【问题讨论】:

    标签: c++ templates type-alias


    【解决方案1】:

    因此,在我们开始挖掘一些基于 SFINAE 的诡计以尝试规避 the unavoidable 之前,请先尝试一下:

    别名模板永远不会被模板参数推导推导出来

    我们可以自己“推导出”编译器的模板参数,如下所示:

    #include <tuple>
    
    template <typename T>
    using Foo = std::tuple<int, T>;
    
    template <template <typename ...> class F, typename T, typename ...Ts>
    void foo(F<T, std::string, Ts...> bar) {}
    
    void test() {
      foo(Foo<std::string>());
    }
    

    所以现在我们为您的foo(Foo&lt;std::string&gt;()); 调用编译它,Foostd::tuple 之上的别名模板,更重要的是,foo() 仍然专门用于Foo&lt;std::string&gt; .

    但是,例如,为了支持同时使用foo() std::tuple 别名模板和包装类,我们仍然没有编译它无错误。如,如果我们现在注释掉 tuple-flavor Foo 并带回包装类 Foo 然后调用我们重写的 foo() 将无法编译

    为了解决这个问题,让我们尝试使用 SFINAE 来救援,并将 foo() 的最后声明替换为以下代码:

    template <template <typename ...> class F, typename T, typename ...Ts,
              typename std::enable_if_t<std::is_same<F<T, Ts...>, 
                  std::tuple<T, Ts...>>::value >* = nullptr>
    void foo(F<T, std::string, Ts...> bar) {}
    
    template <template <typename> class F>
    void foo(F<std::string> bar) {}
    

    现在您可以调用foo() 来获取tuples 的包装类实例和tuples 的别名模板。你也可以用同样的方式为std::variant 实现。

    【讨论】:

    • 我更喜欢这样 - 我正在编写一个基于 Haskell 仿函数的小型仿函数库。我将在我的原始帖子中添加更多上下文。
    • @Taylor 添加了一个编辑以保留完整的要求,因为我已经从你的问题中收集到它。让我知道从您的角度来看它的位置。
    • 感谢您深入研究。我最终选择了包装类,因为我认为using 方法可能会导致更多问题。如果 C++ 提供与 Haskell 的 newtype 等效的东西那就太好了——using 的简单性,但它也创建了一个可以用编译器而不是别名来检查的新类型。
    • 标记为答案,因为我不知道别名不用于类型推断,尽管我现在明白为什么会这样了。
    【解决方案2】:

    与:

    template <typename T> using Foo = std::tuple<int, T>;
    
    template <template <typename> class F> void foo(F<std::string> bar) {}
    
    void test() { foo(Foo<std::string>()); }
    

    Foo&lt;std::string&gt;std::tuple&lt;int, std::string&gt;

    所以test

    void test() { foo(std::tuple<int, std::string>()); }
    

    您希望编译器如何从 tuple 推断它来自哪个别名?

    我们可能有

    template <typename T> using Bar = std::tuple<int, std::string>;
    template <typename T> using Bar2 = std::tuple<some_trait<T>::type, some_trait<T>::type2>;
    // ...
    

    可能的解决方法是:

    template <typename T, typename U>
    Foo<U> fmap(std::function<U(T)> f, Foo<T> value)
    {
        return Foo<U>(std::get<0>(value), f(std::get<1>(value)));   
    }
    

    使用调用语法:

    fmap(fn, Foo<int>(42, 7));
    

    【讨论】:

      猜你喜欢
      • 2018-11-26
      • 1970-01-01
      • 1970-01-01
      • 2016-01-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-29
      相关资源
      最近更新 更多