具有元组类型子集相应参数的模板函数
template function with corresponding parameters to subset of tuple types
我想这样写函数find
:
multi_set<int, string, double, myType> m; //vector of tuples
m.insert(/*some data*/);
m.find<1,2>("something",2.123);
或
m.find<0,3>(1,instanceOfMyType);
m.find<1>("somethingelse");
其中 find
可以对应于元组参数的任何子集进行参数化。
到目前为止我的代码:
template <typename ... T>
class multi_set{
typedef tuple < T... > Tuple;
vector<tuple<T...>> data = vector<tuple<T...>>();
public:
void insert(T... t){
data.push_back(tuple<T...>(t...));
}
template<size_t ... Pos>
void find(???){
// then I would like to use those params to search through data and
// return first matching item
}
}
您应该研究 boost::multi_index
。它非常接近您要找的东西。
http://www.boost.org/doc/libs/1_54_0/libs/multi_index/doc/tutorial/index.html
// test whether a particular tuple is a match
template<size_t... Pos>
static bool is_match(const Tuple& tuple, const typename std::tuple_element<Pos, Tuple>::type &... args) {
std::initializer_list<bool> results = { (std::get<Pos>(tuple) == args)... };
return std::all_of(results.begin(), results.end(), [](bool p) { return p; });
}
// Find the first one that is a match.
template<size_t... Pos>
typename vector<Tuple>::const_iterator find(const typename std::tuple_element<Pos, Tuple>::type &... args) const {
return std::find_if(data.begin(), data.end(), [&](const Tuple & tup) { return is_match<Pos...>(tup, args...); });
}
也可以让 find
接受类型参数包并完美转发,而不是使用 tuple_element
接受固定类型。好处是如果 ==
是透明的,您可以避免不必要的转换。代价是你不能再接受任何不能完美转发的东西(例如,花括号初始化列表,0
作为空指针常量)。附带的好处似乎是 MSVC 2013 不会在此版本上阻塞:
// test whether a particular tuple is a match
template<size_t... Pos, class... Args>
static bool is_match(const Tuple& tuple, Args&&... args) {
std::initializer_list<bool> results = { (std::get<Pos>(tuple) == std::forward<Args>(args))... };
return std::all_of(results.begin(), results.end(), [](bool p) { return p; });
}
// Find the first one that is a match.
template<size_t... Pos, class... Args>
typename vector<Tuple>::const_iterator find(Args&&... args) const {
return std::find_if(data.begin(), data.end(), [&](const Tuple & tup) { return is_match<Pos...>(tup, std::forward<Args>(args)...); });
}
这是一个接受种子值和一组 lambda 的函数。它依次通过每个 lambda 提供种子值:
template<class... Fs, class R>
R chain( R r, Fs&&... fs ) {
using in_order = int[];
(void)(in_order{0,
(
(r = std::forward<Fs>(fs)( r ))
, void(), 0
)...
});
return r;
}
在你的class里面,我们使用上面的:
template<size_t... Pos, class...Us>
typename std::vector<Tuple>::const_iterator
find(Us const&... us) const {
return std::find_if(
data.begin(), data.end(),
[&](const Tuple & tup) {
return chain(
true,
[&](bool old){
return old && (std::get<Pos>(tup) == us);
}...
);
}
);
}
这可以在 clang 中编译,但不能在 g++ 4.9.2 中编译 -- g++ 不喜欢 lambda 中的参数包。
请注意我们采用 Us const&...
——这允许透明 ==
,这在某些情况下很重要。 std::string == char const*
是一个 classic 示例,如果您强制 find
取与元组中相同的值,您将在调用 find
时强制进行不必要的分配。
在 C++1z 中,chain
调用可以替换为:
( ... && (std::get<Pos>(tup) == us) )
这在概念上是相同的,但更容易阅读。这被称为 "fold expression".
现在,上面的一个问题是它使用了转发引用,这导致了完美转发的不完美转发问题。
其中最烦人的是无法使用 {} 构造参数。
如果我们使用匹配类型,我们反而会强制进行非透明比较,这可能会很昂贵(检查 std::string
与 "hello this is a c string"
相比——如果我们强制 c 字符串进入,则可能会导致分配一个std::string
.)
解决此问题的方法是 type erase down to the concept of equality with a given type。
template<class...>struct voider{using type=void;};
template<class...Ts>using void_t=typename voider<Ts...>::type;
template<class T>struct tag{using type=T;};
template<class...>struct types{using type=types;};
template<class T>
using block_deduction = typename tag<T>::type;
template<class F, class Sig, class T=void>
struct erase_view_op;
template<class F, class R, class...Ts, class T>
struct erase_view_op<F, R(Ts...), T>
{
using fptr = R(*)(void const*, Ts&&...);
fptr f;
void const* ptr;
private:
template<class U>
erase_view_op(U&& u, int):
f([](void const* p, Ts&&...ts)->R{
U& u = reinterpret_cast<U&>( *static_cast<std::decay_t<U>*>(const_cast<void*>(p)) );
return F{}( u, std::forward<Ts>(ts)... );
}),
ptr( static_cast<void const*>(std::addressof(u)) )
{}
public:
template<class U, class=std::enable_if_t< !std::is_same<std::decay_t<U>,erase_view_op>{} && std::is_convertible< std::result_of_t<F(U,Ts...)>, R >{} >>
erase_view_op(U&& u):erase_view_op( std::forward<U>(u), 0 ){}
template<class U=T, class=std::enable_if_t< !std::is_same<U, void>{} >>
erase_view_op( block_deduction<U>&& u ):erase_view_op( std::move(u), 0 ){}
erase_view_op( erase_view_op const& ) = default;
erase_view_op( erase_view_op&& ) = default;
R operator()( Ts... ts ) const {
return f( ptr, std::forward<Ts>(ts)... );
}
};
struct equality {
template<class lhs, class rhs>
bool operator()(lhs const& l, rhs const& r)const {
return l==r;
}
};
template<class T>
using erase_equal_to = erase_view_op< equality, bool(T const&), T >;
using string_equal_to = erase_equal_to< std::string >;
int main() {
static_assert( std::is_same< bool, std::result_of_t< std::equal_to<>(decltype("hello"), std::string const&) > >{}, "hmm" );
string_equal_to s = "hello";
string_equal_to s2 = {{"hello"}};
(void)s2;
std::string x = "hello";
std::string y = "jello";
std::cout << s(x) << s(y) << '\n';
}
然后我们重写find
:
template<size_t... Pos>
typename std::vector<Tuple>::const_iterator
find(erase_equal_to< std::remove_reference_t<std::tuple_element_t<Pos, Tuple>> >... us) const {
return std::find_if(
data.begin(), data.end(),
[&](const Tuple & tup) {
return chain(
true,
[&](bool old){
return old && us(std::get<Pos>(tup));
}...
);
}
);
}
既透明相等又允许基于 {}
的构造(好吧,它确实需要基于 {{}}
的构造——外面说我们正在构造橡皮擦,内部构造 T
).
我想这样写函数find
:
multi_set<int, string, double, myType> m; //vector of tuples
m.insert(/*some data*/);
m.find<1,2>("something",2.123);
或
m.find<0,3>(1,instanceOfMyType);
m.find<1>("somethingelse");
其中 find
可以对应于元组参数的任何子集进行参数化。
到目前为止我的代码:
template <typename ... T>
class multi_set{
typedef tuple < T... > Tuple;
vector<tuple<T...>> data = vector<tuple<T...>>();
public:
void insert(T... t){
data.push_back(tuple<T...>(t...));
}
template<size_t ... Pos>
void find(???){
// then I would like to use those params to search through data and
// return first matching item
}
}
您应该研究 boost::multi_index
。它非常接近您要找的东西。
http://www.boost.org/doc/libs/1_54_0/libs/multi_index/doc/tutorial/index.html
// test whether a particular tuple is a match
template<size_t... Pos>
static bool is_match(const Tuple& tuple, const typename std::tuple_element<Pos, Tuple>::type &... args) {
std::initializer_list<bool> results = { (std::get<Pos>(tuple) == args)... };
return std::all_of(results.begin(), results.end(), [](bool p) { return p; });
}
// Find the first one that is a match.
template<size_t... Pos>
typename vector<Tuple>::const_iterator find(const typename std::tuple_element<Pos, Tuple>::type &... args) const {
return std::find_if(data.begin(), data.end(), [&](const Tuple & tup) { return is_match<Pos...>(tup, args...); });
}
也可以让 find
接受类型参数包并完美转发,而不是使用 tuple_element
接受固定类型。好处是如果 ==
是透明的,您可以避免不必要的转换。代价是你不能再接受任何不能完美转发的东西(例如,花括号初始化列表,0
作为空指针常量)。附带的好处似乎是 MSVC 2013 不会在此版本上阻塞:
// test whether a particular tuple is a match
template<size_t... Pos, class... Args>
static bool is_match(const Tuple& tuple, Args&&... args) {
std::initializer_list<bool> results = { (std::get<Pos>(tuple) == std::forward<Args>(args))... };
return std::all_of(results.begin(), results.end(), [](bool p) { return p; });
}
// Find the first one that is a match.
template<size_t... Pos, class... Args>
typename vector<Tuple>::const_iterator find(Args&&... args) const {
return std::find_if(data.begin(), data.end(), [&](const Tuple & tup) { return is_match<Pos...>(tup, std::forward<Args>(args)...); });
}
这是一个接受种子值和一组 lambda 的函数。它依次通过每个 lambda 提供种子值:
template<class... Fs, class R>
R chain( R r, Fs&&... fs ) {
using in_order = int[];
(void)(in_order{0,
(
(r = std::forward<Fs>(fs)( r ))
, void(), 0
)...
});
return r;
}
在你的class里面,我们使用上面的:
template<size_t... Pos, class...Us>
typename std::vector<Tuple>::const_iterator
find(Us const&... us) const {
return std::find_if(
data.begin(), data.end(),
[&](const Tuple & tup) {
return chain(
true,
[&](bool old){
return old && (std::get<Pos>(tup) == us);
}...
);
}
);
}
这可以在 clang 中编译,但不能在 g++ 4.9.2 中编译 -- g++ 不喜欢 lambda 中的参数包。
请注意我们采用 Us const&...
——这允许透明 ==
,这在某些情况下很重要。 std::string == char const*
是一个 classic 示例,如果您强制 find
取与元组中相同的值,您将在调用 find
时强制进行不必要的分配。
在 C++1z 中,chain
调用可以替换为:
( ... && (std::get<Pos>(tup) == us) )
这在概念上是相同的,但更容易阅读。这被称为 "fold expression".
现在,上面的一个问题是它使用了转发引用,这导致了完美转发的不完美转发问题。
其中最烦人的是无法使用 {} 构造参数。
如果我们使用匹配类型,我们反而会强制进行非透明比较,这可能会很昂贵(检查 std::string
与 "hello this is a c string"
相比——如果我们强制 c 字符串进入,则可能会导致分配一个std::string
.)
解决此问题的方法是 type erase down to the concept of equality with a given type。
template<class...>struct voider{using type=void;};
template<class...Ts>using void_t=typename voider<Ts...>::type;
template<class T>struct tag{using type=T;};
template<class...>struct types{using type=types;};
template<class T>
using block_deduction = typename tag<T>::type;
template<class F, class Sig, class T=void>
struct erase_view_op;
template<class F, class R, class...Ts, class T>
struct erase_view_op<F, R(Ts...), T>
{
using fptr = R(*)(void const*, Ts&&...);
fptr f;
void const* ptr;
private:
template<class U>
erase_view_op(U&& u, int):
f([](void const* p, Ts&&...ts)->R{
U& u = reinterpret_cast<U&>( *static_cast<std::decay_t<U>*>(const_cast<void*>(p)) );
return F{}( u, std::forward<Ts>(ts)... );
}),
ptr( static_cast<void const*>(std::addressof(u)) )
{}
public:
template<class U, class=std::enable_if_t< !std::is_same<std::decay_t<U>,erase_view_op>{} && std::is_convertible< std::result_of_t<F(U,Ts...)>, R >{} >>
erase_view_op(U&& u):erase_view_op( std::forward<U>(u), 0 ){}
template<class U=T, class=std::enable_if_t< !std::is_same<U, void>{} >>
erase_view_op( block_deduction<U>&& u ):erase_view_op( std::move(u), 0 ){}
erase_view_op( erase_view_op const& ) = default;
erase_view_op( erase_view_op&& ) = default;
R operator()( Ts... ts ) const {
return f( ptr, std::forward<Ts>(ts)... );
}
};
struct equality {
template<class lhs, class rhs>
bool operator()(lhs const& l, rhs const& r)const {
return l==r;
}
};
template<class T>
using erase_equal_to = erase_view_op< equality, bool(T const&), T >;
using string_equal_to = erase_equal_to< std::string >;
int main() {
static_assert( std::is_same< bool, std::result_of_t< std::equal_to<>(decltype("hello"), std::string const&) > >{}, "hmm" );
string_equal_to s = "hello";
string_equal_to s2 = {{"hello"}};
(void)s2;
std::string x = "hello";
std::string y = "jello";
std::cout << s(x) << s(y) << '\n';
}
然后我们重写find
:
template<size_t... Pos>
typename std::vector<Tuple>::const_iterator
find(erase_equal_to< std::remove_reference_t<std::tuple_element_t<Pos, Tuple>> >... us) const {
return std::find_if(
data.begin(), data.end(),
[&](const Tuple & tup) {
return chain(
true,
[&](bool old){
return old && us(std::get<Pos>(tup));
}...
);
}
);
}
既透明相等又允许基于 {}
的构造(好吧,它确实需要基于 {{}}
的构造——外面说我们正在构造橡皮擦,内部构造 T
).