C++ 使用可变模板和lambda函数的二进制搜索
想想这个,C++ 使用可变模板和lambda函数的二进制搜索,c++,templates,c++11,binary,variadic,C++,Templates,C++11,Binary,Variadic,想想这个, struct Person { std::string name; Person (const std::string& n) : name(n) {} std::string getName(int, char) const {return name;} // int, char play no role in this // simple example, but let's suppose that they are neede
struct Person {
std::string name;
Person (const std::string& n) : name(n) {}
std::string getName(int, char) const {return name;} // int, char play no role in this
// simple example, but let's suppose that they are needed.
} *Bob = new Person("Bob"), *Frank = new Person("Frank"), *Mark = new Person("Mark"),
*Tom = new Person("Tom"), *Zack = new Person("Zack");
const std::vector<Person*> people = {Bob, Frank, Mark, Tom, Zack};
因此,模板函数binarySearch
可以通用。我是通过以下方式实现的:
#include <iostream>
#include <string>
#include <vector>
#include <functional>
struct Person {
std::string name;
Person (const std::string& n) : name(n) {}
std::string getName(int, char) const {return name;} // int, char play no role in this
// simple example, but let's supposes that they are needed.
} *Bob = new Person("Bob"), *Frank = new Person("Frank"), *Mark = new Person("Mark"),
*Tom = new Person("Tom"), *Zack = new Person("Zack");
const std::vector<Person*> people = {Bob, Frank, Mark, Tom, Zack};
template <typename Container, typename Ret>
typename Container::value_type binarySearch (const Container& container, const Ret& value,
std::function<Ret(const typename Container::value_type&, int, char)> f,
std::function<bool(const Ret&, const Ret&)> comp,
typename Container::difference_type low, typename Container::difference_type high,
int n, char c) {
if (low > high)
std::cout << "Error! Not found!\n";
const typename Container::difference_type mid = (low + high) / 2;
const Ret& r = f(container[mid], n, c);
if (r == value)
return container[mid];
if (comp(r, value))
return binarySearch (container, value, f, comp, mid + 1, high, n, c);
return binarySearch (container, value, f, comp, low, mid - 1, n, c);
}
template <typename Container, typename Ret>
typename Container::value_type binarySearch (const Container& container, const Ret& value,
std::function<Ret(const typename Container::value_type&, int, char)> f,
std::function<bool(const Ret&, const Ret&)> comp, int n, char c) {
return binarySearch (container, value, f, comp, 0, container.size() - 1, n, c);
}
int main() {
const Person* person = binarySearch<std::vector<Person*>, std::string>
(people, "Tom", &Person::getName,
[](const std::string& x, const std::string& y) {return x.compare(y) < 0;}, 5, 'a');
std::cout << person->getName(5,'a') << '\n'; // Tom
}
通用条款4.9.2:
[Error] no matching function for call to 'binarySearch(std::vector<Person*>&, const char [4], main()::__lambda0, main()::__lambda1, int, char)'
template argument deduction/substitution failed:
[Note] 'main()::__lambda0' is not derived from 'std::function<std::basic_string<char>(Person* const&, Args ...)>'
[Error]调用“binarySearch(std::vector&,const char[4],main():_lambda0,main():_lambda1,int,char)”时没有匹配的函数
模板参数扣除/替换失败:
[注意]“main()::_lambda0”不是从“std::function”派生的
更新:
在研究了雅克的解决方案后,我将我的解决方案改编为以下内容(使用更多的第一原则,而不是std::equal_range):
#包括
#包括
模板
typename容器::值\类型binarySearchRandomAccessIterator(常量容器和容器,T&&值,比较器和比较,typename容器::差异\类型低,typename容器::差异\类型高){
如果(低>高)
{std::cout 0){//使用迭代器执行二进制搜索。
它=第一;
步骤=计数/2;
std::advance(it,step);//这是在O(step)时间内完成的,因为ForwardIterator不是一个随机访问迭代器(否则它是在固定时间内完成的)。但好消息是,每次循环迭代,“step”都会变小一半。
const auto&t=compare.function(*it);//使用“const t&t”不会编译。
根据compare.comparator,如果(compare.comparator(t,value)){/'t'小于'value',则在上半部分搜索。
first=++it;//因此first将移动到超过中间点的位置,我们从那里开始搜索。
计数-=步数+1;//计数减半加1。
}
根据compare.comparator,else//'t'大于'value',因此保留在下半部分。
计数=步长;//“计数”和“步长”都减少一半。
}
if(比较函数(*first)!=值)
std::cout::type
binarySearch(const容器和容器,T&&value,Comparator&&compare={}){
在我看来,std::cout
Person* person = binarySearch (people, "Tom",
[](Person* p, int n, char c) {return p->getName(n,c);},
[](const std::string& x, const std::string& y) {return x.compare(y) < 0;}, 5, 'a');
identity>
您的代码将开始编译(因为Args…
是在其他地方推导的)。identity
阻止对该参数进行模板类型推导,因此编译器不再尝试,而是从其他参数推断类型
然而,这并不是一个好的解决方案
一个更好的解决方案是将f
和c
的类型设置为f
和c
——根本不要将它们设置为std::function
s。这消除了无意义的类型擦除开销,也消除了对标识的需求
这仍然不是一个好的解决方案,因为您的模板函数可以做很多事情,但很少能做得很好。相反,请将您的操作分解为更简单的问题,然后将它们组合在一起:
首先,我们已经有了std::equal_range
,这将是一个比您可能编写的任何一个更好的二进制搜索。编写一个返回单个元素并使用容器的函数似乎是合理的,因为使用迭代器很烦人
为了实现这一点,我们首先编写一些基于范围的样板:
namespace adl_aux {
using std::begin; using std::end;
template<class R>
auto adl_begin(R&&)->decltype(begin(std::declval<R>()));
template<class R>
auto adl_end(R&&)->decltype(end(std::declval<R>()));
}
template<class R>
using adl_begin = decltype(adl_aux::adl_begin(std::declval<R>));
template<class R>
using adl_end = decltype(adl_aux::adl_end(std::declval<R>));
template<class R>using iterator_t = adl_begin<R>;
template<class R>using value_t = std::remove_reference_t<decltype(*std::declval<iterator_t<R>>())>;
template<class R, class T, class F=std::less<T>>
value_t<R>* bin_search( R&& r, T&& t, F&& f={} ) {
using std::begin; using std::end;
auto range = std::equal_range( begin(r), end(r), std::forward<T>(t), std::forward<F>(f) );
if (range.first==range.second) return nullptr;
return std::addressof( *range.first ); // in case someone overloaded `&`
}
它返回一个指向排序下元素t
的指针f
假定R
在其下排序(如果存在),否则nullptr
下一部分是您的订单:
[](Person* p, int n, char c) {return p->getName(n,c);},
[](const std::string& x, const std::string& y) {return x.compare(y) < 0;}, 5, 'a'
如果您真的需要在一行上进行绑定,请直接进行绑定
下一步,我们需要订购人
:
template<class F, class C>
struct order_by_t : private F, private C {
F const& f() const { return *this; }
C const& c() const { return *this; }
template<class T>
auto f(T&&t)const
->decltype( std::declval<F const&>()(std::declval<T>()) )
{
return f()(std::forward<T>(t));
}
template<class T, class... Unused> // Unused to force lower priority
auto f(T&&t, Unused&&... ) const
-> std::decay_t<T>
{ return std::forward<T>(t); }
template<class Lhs, class Rhs>
bool operator()(Lhs&& lhs, Rhs&& rhs) const {
return c()( f(std::forward<Lhs>(lhs)), f(std::forward<Rhs>(rhs)) );
}
template<class F0, class C0>
order_by_t( F0&& f_, C0&& c_ ):
F(std::forward<F0>(f_)), C(std::forward<C0>(c_))
{}
};
template<class C=std::less<>, class F>
auto order_by( F&& f, C&& c={} )
-> order_by_t<std::decay_t<F>, std::decay_t<C>>
{ return {std::forward<F>(f), std::forward<C>(c)}; }
现在是符合您要求的Person const*
s上的订单
然后我们将其输入到bin\u search
:
auto ordering = order_by(
[](int n, char c){
return [n,c](Person const* p)
->decltype(p->getName(n,c)) // SFINAE enabled
{return p->getName(n,c);}
}(5,'a'),
[](const std::string& x, const std::string& y) {return x.compare(y) < 0;}
);
Person*const* p = bin_search( people, "Tom", ordering );
是丑陋的,可以替换为:
[](Person const* p)
->decltype(p->getName(5,'a')) // SFINAE enabled
{return p->getName(5,'a');}
另外,由于lambda的参数检查已经足够了,我们可以删除SFINAE显式返回类型:
[](Person const* p)
{return p->getName(5,'a');}
我们完成了
甚至:
Person*const* p = bin_search( people, "Tom",
order_by( [](Person const* p) {return p->getName(5,'a');} )
);
看起来不那么难看,不是吗
哦,还有:
using std::literals;
Person*const* p = bin_search( people, "Tom"s,
order_by( [](Person const* p) {return p->getName(5,'a');} )
);
可能具有更好的性能,因为它可以避免在每次比较时重复构造std::string(“Tom”)
。类似地,返回std::string常量的getName
(如果可能)也可以提高性能。“投影lambda”可能必须具有->decltype(自动)
,实现第二次提升
我在上面使用了一些C++14。可以将std::remove\u reference\u t
(和类似的)别名替换为typename std::remove\u reference::type
,或者您可以编写自己的\u t
别名。在C++11中,使用decltype(auto)
的建议可以替换为decltype(返回表达式)
order\u by\u t
使用继承来存储F
和C
,因为它们可能是空类,所以我想利用空基优化。我不清楚您想要完成什么。一个通用的二进制搜索函数,它接受容器并查找lambda函数和一个值。当你说不能用Args…Args替换int,char时,你真的在函数的模板参数列表中包含了可变模板参数吗?你得到了什么错误?获取类型为std::function
的函数参数几乎从来都不是你真正想要的。
order_by(
[](int n, char c){
return [n,c](Person const* p)
->decltype(p->getName(n,c)) // SFINAE enabled
{return p->getName(n,c);};
}(5,'a'),
[](const std::string& x, const std::string& y) {return x.compare(y) < 0;}
}
auto ordering = order_by(
[](int n, char c){
return [n,c](Person const* p)
->decltype(p->getName(n,c)) // SFINAE enabled
{return p->getName(n,c);}
}(5,'a'),
[](const std::string& x, const std::string& y) {return x.compare(y) < 0;}
);
Person*const* p = bin_search( people, "Tom", ordering );
[](int n, char c){
return [n,c](Person const* p)
->decltype(p->getName(n,c)) // SFINAE enabled
{return p->getName(n,c);}
}(5,'a'),
[](Person const* p)
->decltype(p->getName(5,'a')) // SFINAE enabled
{return p->getName(5,'a');}
[](Person const* p)
{return p->getName(5,'a');}
auto ordering = order_by(
[](Person const* p)
{return p->getName(5,'a');}
);
Person*const* p = bin_search( people, "Tom", ordering );
Person*const* p = bin_search( people, "Tom",
order_by( [](Person const* p) {return p->getName(5,'a');} )
);
using std::literals;
Person*const* p = bin_search( people, "Tom"s,
order_by( [](Person const* p) {return p->getName(5,'a');} )
);