【问题标题】:Translating a std::tuple into a template parameter pack将 std::tuple 转换为模板参数包
【发布时间】:2012-12-11 14:42:11
【问题描述】:

我有一个

typedef std::tuple<A, B> TupleType;

并且想使用类列表 对于“模板”。

假设我有:

template<typename... args>
std::tuple<args...> parse(std::istream &stream) {
  return std::make_tuple(args(stream)...);
}

并且我可以成功地使用它:

auto my_tuple = parse<A, B>(ifs);

如果我已经有一个,是否可以避免指定类列表 A,B

typedef std::tuple<A,B> TupleType;

列表 A,B 已经在哪里了?

一个例子:

#include <cstdlib>  // EXIT_SUCCESS, EXIT_FAILURE
#include <iostream> // std::cerr
#include <fstream>  // std::ifstream
#include <tuple>    // std::tuple

class A {
public:
  A(std::istream &);  // May throw FooBaarException 
};

class B {
public:
  B(std::istream &); // May throw FooBaarException 
};

template<typename... args>
std::tuple<args...> parse(std::istream &stream) {
  return std::make_tuple(args(stream)...);
}

int main() {
  std::ifstream ifs;
  ifs.exceptions(ifstream::eofbit | ifstream::failbit | ifstream::badbit);
  int res = EXIT_FAILURE;
  try {
    ifs.open("/some/file/path", std::ios::in | std::ios::binary);
    auto my_tuple = parse<A, B>(ifs); // my_tuple is of the type std::tuple<A,B>
    /* Here do something interesting with my_tuple */ 
    res = EXIT_SUCCESS;
  } catch (ifstream::failure e) {
    std::cerr << "error: opening or reading file failed\n";
  } catch (FooBaarException e) {
    std::cerr << "error: parsing in a constructor failed\n";
  }
  return res;
}

【问题讨论】:

  • 看来在构造函数中你想从字符串中读取。请注意,对于 parse 的实现,未指定构造函数的调用顺序。
  • @JohannesSchaub-litb,有趣的一点。也可以这样做:liveworkspace.org/code/MTk2Nj$0,前提是组件类型是不同的(可能但太长,无法作为重复类型的示例显示)。
  • 7 年后...考虑 std::apply 自 C++17 以来。

标签: c++ c++11 tuples variadic-templates


【解决方案1】:

您的情况的根本问题似乎是,当模板参数为std::tuple 时,您希望将函数模板parse 专门用于特殊情况。不幸的是,函数模板无法实现这种专业化。

但是,可以使用类模板。

因此,作为第一步,您可以将parse 定义为struct 的静态函数,如下所示:

using std::istream;
using std::tuple;
using std::make_tuple;

struct A { A(const istream &) {} };
struct B { B(const istream &) {} };

template <typename... Args>
struct parser
{
  /* Your original function, now inside a struct.
     I'm using direct tuple construction and an
     initializer list to circumvent the order-of-
     construction problem mentioned in the comment
     to your question. */
  static tuple<Args...> parse(const istream &strm)
  { return tuple<Args...> {Args(strm)...}; }
};

template <typename... Args>
struct parser<tuple<Args...>>
{
  /* Specialized for tuple. */
  static tuple<Args...> parse(const istream &strm)
  { return parser<Args...>::parse(strm); }
};

然后你可以用你想要的方式调用它:

int main()
{
  typedef tuple<A,B> tuple_type;
  auto tup = parser<tuple_type>::parse(std::cin);
  return 0;
}

作为第二步,您可以(再次)定义一个函数模板,它将参数传递给结构的正确特化:

template <typename... Args>
auto parse(const istream &strm) -> decltype(parser<Args...>::parse(strm))
{ return parser<Args...>::parse(strm); }

现在你可以按照你想要的方式使用它了:

int main()
{
  typedef tuple<A,B> tuple_type;
  auto tup = parse<tuple_type>(std::cin);
  return 0;
}

(您仍然可以以旧方式使用它:auto tup = parse&lt;A,B&gt;(std::cin)。)


备注。正如 parser::parse() 的评论中提到的,我使用直接元组构造而不是 make_tuple 来避免元组元素的构造顺序问题。这与您的问题没有直接关系,而是一件好事。见how to avoid undefined execution order for the constructors when using std::make_tuple

【讨论】:

  • “不幸的是,这种专业化对于函数模板是不可能的。”不完全正确。嗯,这是真的,但你可以从重载和模板推导中获得几乎相同的效果。见这里:liveworkspace.org/code/MjU4Nj$0
  • @rici 我认为您无法使用重载来解决问题中描述的问题,因为必须保持原样的parse 的现有版本非常普遍,以至于它会导致歧义。但是用于结构/类的机制是部分模板特化的机制,它可以消除如上所述的歧义。
  • TBH,我不知道他想做什么。这个问题我看了好几遍,还是不明白他要呈现什么样的界面。但是,当然可以通过向 parse 添加未评估的参数来使用演绎,这有时是一种有用的技术。 (例如auto a = parse(ifs, Into&lt;some_tuple&gt;());,可以在不改变现有接口的情况下添加。)(我同意在这种情况下,结构更好,这就是我自己使用它的原因:))
  • Into&lt;some_tuple&gt;() 将被评估(尽管它可能编译为无操作)。 (但是是的,改变函数的接口——或者,就此而言,改变函数的名称——总是解决歧义问题的方法。)
  • 您的解决方案与@rici 的答案非常相似。选择被接受的答案是一个艰难的选择。
【解决方案2】:

这种事情有一个标准的成语。 [1]

// Define the "shape" of the template
template<typename Tuple> struct TupleMap;
// Specialize it for std::tuple
template<typename...T> struct TupleMap<std::tuple<T...>> {
  using type = std::tuple<T...>;  // not necessary but saves typing
  // ... inside here, you have access to the parameter pac
}

这是一个使用它的示例,它可能符合您的期望,也可能不符合您的期望(您的示例并未真正表明您的预期用途,因为它缺少您在问题中承诺的typedef):liveworkspace.org

由于litb 提出了这一点,因此可以强制以从左到右的顺序构造元组组件,这说明了另一个有趣的习语:梳状继承。见lws

(因为lws可能会再次消失,谁知道呢,我把代码也贴在这里):

#include <iostream>
#include <tuple>
#include <type_traits>
#include <utility>

// Define the "shape" of the template
template<typename Tuple> struct TupleMap;
// Specialize it for std::tuple
template<typename...T> struct TupleMap<std::tuple<T...>> {
   using type = std::tuple<T...>;  // not necessary but saves typing

   type value;

   template<typename Arg>
   TupleMap(Arg&& arg)
       : value(T(std::forward<Arg>(arg))...) {
   }

   operator type() { return value; }
};

//Try it out:
using std::get;  // Note 2
using Numbers = std::tuple<char, double, int>;

// Note 3
std::ostream& operator<<(std::ostream& out, const Numbers& n) {
   return out << get<0>(n) << ' ' << get<1>(n) << ' ' << get<2>(n);
}

int main() {
    std::cout << TupleMap<Numbers>(93.14159);
    return 0;
}

[1] 至少,我认为这是一个标准的成语。我经常使用它,并将其视为“开罐器”模式。

[2] 这是必需的(或者至少,这是我的风格),以允许将get 与在std 之外定义的类似元组的模板一起使用。这样做可以让 ADL 找到 get 的适当定义,而不会强迫我向 std::get 添加专业化。这样,它类似于beginend 的标准ADL 习语。

[3] 您可以在 SO 中搜索一个很酷的 hack,专门针对所有元组使用 operator&lt;&lt;。有一个更简单的可以用于特定元组,但这对于这个问题来说都是题外话,所以我只是做了一些简单且无依赖的事情。请注意,这是因为 TupleMap 中的转换运算符而起作用的

【讨论】:

  • +1 用于使用结构进行专业化。 (我发布自己的答案的原因是我认为 OP 代码中的 parse 函数应该转换为结构的静态函数,而不是调用结构的构造函数。)
  • @jogojapan 合法的设计决策。不过,我不认为这有什么不同。但口味不同。你可能想考虑一下 litb 关于构造顺序的观点,除非你已经在 lws 上看过我的版本。
【解决方案3】:

基本方法是创建索引序列0, ..., std::tuple_size&lt;Tuple&gt;::value - 1 作为参数包Indices 并使用parse&lt;typename std::tuple_element&lt;Tuple, Indices&gt;::type...&gt;(stream) 调用您的函数。您可能会将逻辑封装到一个函数 parse_tuple&lt;Tuple&gt;(stream)(以及这个委托给的一些函数)中,该函数最终委托给 parse&lt;...&gt;(stream)

首先,这是一个类模板和一个函数,用于根据std::tuple 的大小创建索引序列。需要索引才能从std::tuple 获取类型列表:

template <int... Indices> struct indices;
template <> 
struct indices<-1> {                // for an empty std::tuple<> there is no entry
    typedef indices<> type;
};
template <int... Indices>
struct indices<0, Indices...> {     // stop the recursion when 0 is reached
    typedef indices<0, Indices...> type;
};
template <int Index, int... Indices>
struct indices<Index, Indices...> { // recursively build a sequence of indices
    typedef typename indices<Index - 1, Index, Indices...>::type type;
};

template <typename T>
typename indices<std::tuple_size<T>::value - 1>::type const*
make_indices() {
    return 0;
}

有了这个,很容易从std::tuple&lt;T...&gt;中提取类型序列:

template<typename T, int... Indices>
T parse_tuple(std::istream &stream, indices<Indices...> const*) {
    return parse<typename std::tuple_element<Indices, T>::type...>(stream);
}
template <typename T>
T parse_tuple(std::istream& stream) {
    return parse_tuple<T>(stream, make_indices<T>());
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-11
    • 2012-05-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多