我有一个
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;
}
答案 0 :(得分:6)
您的情况中的潜在问题似乎是,当模板参数为parse
时,您希望专门针对特殊情况使用函数模板std::tuple
。不幸的是,功能模板无法实现这种专业化。
但是,可以使用类模板。
因此,作为第一步,您可以将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<A,B>(std::cin)
。)
备注。正如解析器:: parse()的注释中所提到的,我使用直接元组构造而不是make_tuple
来避免元组元素构造顺序的问题。这与您的问题没有直接关系,但这是一件好事。请参阅how to avoid undefined execution order for the constructors when using std::make_tuple。
答案 1 :(得分: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
):{{3} }。
由于liveworkspace.org提出了这一点,因此可以强制以从左到右的顺序构造元组组件,这说明了另一个有趣的习语:梳子继承。请参阅litb。
(因为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
添加特化。通过这种方式,它类似于begin
和end
的标准ADL惯用语。
[3]你可以搜索SO以获得一个很酷的黑客,以便为所有元组专门化operator<<
。有一个更简单的可用于特定元组,但这对于这个问题都是偏离主题的,所以我只是做了一些简单且无依赖的事情。请注意,这是因为TupleMap
答案 2 :(得分:1)
基本方法是创建一系列索引0, ..., std::tuple_size<Tuple>::value - 1
作为参数包Indices
,并使用parse<typename std::tuple_element<Tuple, Indices>::type...>(stream)
调用您的函数。您可能会将逻辑封装到函数parse_tuple<Tuple>(stream)
(以及一个委托给它的函数)中,该函数最终委托给parse<...>(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<T...>
中提取类型序列:
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>());
}