具有相应参数的模板函数到元组类型的子集

时间:2015-04-08 19:54:21

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

我想写函数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
    }
}

4 个答案:

答案 0 :(得分:5)

// 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)...); });
}

答案 1 :(得分:1)

您应该查看boost::multi_index。它非常接近您所寻找的。

http://www.boost.org/doc/libs/1_54_0/libs/multi_index/doc/tutorial/index.html

答案 2 :(得分:1)

这是一个获取种子值和一组lambdas的函数。它依次通过每个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;
}

在课堂上,我们使用上述内容:

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 ++不喜欢lambdas中的参数包。

请注意我们采用的事实Us const&... - 这允许透明==,这在某些情况下很重要。 std::string == char const*是一个典型示例,如果您强制find采用与元组中相同的值,则会在调用find时强制进行不必要的分配。


在C ++ 1z中,chain调用可以替换为:

( ... && (std::get<Pos>(tup) == us) )

在概念上相同,但更容易阅读。这被称为“折叠表达”。


现在,上面的一个问题是它使用转发引用,这会导致完美转发的不完美转发问题。

最令人烦恼的是无法使用{}来构造参数。

如果我们使用匹配类型,我们会强制进行非透明比较,这可能很昂贵(检查std::string"hello this is a c string"相比 - 如果我们强制将c字符串转换为{ {1}}。)

解决这个问题的方法是type erase down to the concept of equality with a given type

std::string

然后我们重写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)); }... ); } ); } 的构造(嗯,它确实需要基于{}的构造 - 外部表示我们正在构造橡皮擦,内部构造{{1 }})。

答案 3 :(得分:-2)

您的find功能的签名将是

template<size_t ... Pos, typename ... Types> void find(Types... &t)

搜索的实现取决于您,您可以使用递归模板或循环参数包