与嵌套模板一起使用模板

时间:2018-08-24 04:27:15

标签: c++ templates type-alias

以下代码不起作用,因为推断的模板参数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的functor类型类松散地构建一个小的functor库。我已经定义了“类型类”:

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方法可以使之工作。

>

该库主要是出于好奇,也是我探索一些更高级的模板概念的一种手段-感谢您的帮助!

2 个答案:

答案 0 :(得分:2)

因此,在我们开始探索一些基于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<std::string>());呼叫进行编译,其中Foostd::tuple上的别名模板,更重要的是foo()仍然仅适用于Foo<std::string>

例如,为了同时支持foo() 别名模板和包装类的使用std::tuple ,我们仍然不支持没有编译错误。与之类似,如果我们现在注释掉tuple风味Foo并带回包装器类Foo,则调用我们重写的foo() 将不会编译< / strong>。

要解决此问题,请尝试使用 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()的包装类实例和tuple别名模板实例调用tuple。您也可以为std::variant用相同的方式实现。

答案 1 :(得分:1)

使用:

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<std::string>std::tuple<int, std::string>

所以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));