Loading... 相比于一些经典的idioms,这里更多的是整理一些简单、常见的小tricks。原型大多数来自群友的提问。其中有实际意义的,我会把我(或者其他神仙)的解决方案整理出来。这些东西可以让你不必学习大量知识,就能快速找到解决某些实际需求的方法。 # 1 函数转发器(包装器) 写完以后才发现问题说的是参数已经在一个native array里了……那就直接`std::apply`就可以了,这个做法其实应对的是更复杂的情况。 ## 背景 ![image.png](https://zclll.com/usr/uploads/2022/10/3095918026.png) ## 实现 ```cpp void f(string arg0, int arg1, char arg2) // 假设这是func原型 { std::cout<<arg0<<arg1<<arg2; } template <typename... Args> void f_decorator(Args&&... args) { f(std::forward<Args>(args)...); } int main() { f_decorator("123", 456, '7'); return 0; } ``` ## 运行结果 ```plaintext 1234567 ``` ## 解释 原本的函数不能直接方便地实现,我们就考虑引入一个包装器进行**桥接**。既然问题在于涉及不同类型,我们就考虑使用**模板形参包**来兼容。当然,这里别忘了用`forward`做**完美转发**,这个作用讲过就不再赘述了。 # 2 屏蔽ADL查找 ## 背景 ![image.png](https://zclll.com/usr/uploads/2022/10/462093869.png) ## 实现 比如这样: ```cpp #include<iostream> namespace local{ struct A{}; void f(A){ std::cout<<"local\n"; } } template<typename T> void f(T) { std::cout<<"global\n"; } int main() { local::A x; f(x); ::f(x); return 0; } ``` ## 运行结果 ```plaintext local global ``` ## 解释 `f(x)`会触发ADL查找,因此实参所在的`local`空间会被搜索,其中的`f(A)`会被加入候选函数,由于它比`template f(T)`更为特殊,因此成为最佳匹配函数。 而`::f(x)`是有限定查找,限定查找的含义是必须在所限定的空间中进行查找,因此ADL的过程被跳过了。而空限定名指示的是当前可见的空间,因此只找到了`template f(T)`这一匹配。 这也就是“空限定名字查找”作用的简明一例。 哦当然,更进一步思考,就自然产生了CPO(定制点对象)的概念,这个概念及其实现非常复杂,我会在单独的文章中进行说明。 # 3 外部修改私有成员(Don't do this) ## 背景 ![image.png](https://zclll.com/usr/uploads/2022/10/3400769454.png) ## [实现1](https://sf-zhou.github.io/programming/cpp_access_private_member.html) 糖宝找到的方法! ```cpp #include <cassert> #include <iostream> class A { public: int X() { return x_; } private: int x_; }; int A::*FiledPtr(); template <int A::*M> struct Rob { friend int A::*FiledPtr() { return M; } }; template struct Rob<&A::x_>; int main() { A o; o.*FiledPtr() = 10; assert(o.X() == 10); } ``` ### 相关讨论 由上述代码我们可以看到,explicit instantiation可以打破access control,这显然是一种非常危险的行为。所以,即使存在这样的手段,也**永远不要这样做**。 那么在明确了这个前提下,我们再来讨论为什么committee允许了这个做法的存在,如果不允许又会发生什么。 [这个SO回答](https://stackoverflow.com/a/23922329/19247642)给了我们一个例子: ```cpp class Foo { private: struct Bar; template<typename T> class Baz { }; public: void f(); // does things with Baz<Bar> }; // explicit instantiation declaration extern template class Foo::Baz<Foo::Bar>; ``` 可以看到,如果我们在显式实例化的时候考虑access control的限制,那么上述正常功能的导出就不再能够正常运行了。更详细的内容,我们单独用一篇文章来讨论一下,见[committee的取舍——为什么显式实例化可以打破访问控制](https://zclll.com/index.php/cpp/266.html)。 ## protected基于继承的实现 如果要修改的对象不是private而是protected的,那么我们还有一个更为简便的[做法](https://stackoverflow.com/a/1044437/19247642): ```cpp std::deque<int> &getAdapted(std::stack<int> &s) { struct voyeur : stack<int> { using stack<int>::c; }; return s.*(&voyeur::c); } int main() { std::stack<int> s; std::deque<int> &adapted = getAdapted(s); output(adapted); // print the stack... } ``` 这种做法通过继承来实现,相对是比较好想的。 # 4 views 与 pipeline 在[专门的文章](https://zclll.com/index.php/cpp/270.html)中我们已经叙述过了,pipeline的起始端可以直接传入`range`去给后随的`RangeAdaptor`做参数,所以以下写法都是可以并且等价的: ```cpp for (auto x: arr | views::take(3)) std::cout << x; for (auto x: views::all(arr) | views::take(3)) std::cout << x; for (auto x: views::take(arr, 3)) std::cout << x; ``` 而`ranges::xxx_view`和`ranges::views::xxx`(后者作为niebloid对象固定找到前者)也是等价的: ```cpp auto less_than_five_view = take_while_view(arr, [](int x) { return x < 5; }); for (auto x: less_than_five_view | views::reverse) std::cout << x << std::endl; for (auto x: arr | views::take_while([](int x) { return x < 5; }) | views::reverse) std::cout << x << std::endl; ``` 具体如何将闭包添加到对应位置,上述文章中也已经讲明了。 # 5 函数串联 ```cpp template<typename F> F combine(F&& f) { return std::forward<F>(f); } template<typename F, typename...Fs> auto combine(F&& f, Fs&& ...fs) { auto&& rest = combine(std::forward<Fs>(fs)...); return [=](auto&& arg) { return f(rest(std::forward<decltype(arg)>(arg))); }; } ``` 基本的一个形参包展开的模式。这种模式在各种地方都很常用。终止条件是第一个模板。我们先递归组合其它函数,最后我们实际上返回的是一个lambda,它是嵌套调用的接口(接收`arg`)然后完成实际的递归组合操作。另一方面,这种工具函数用到的转发时机,感觉对于理解`forward`也是比较重要的。 © 允许规范转载 打赏 赞赏作者 赞 1 如果觉得我的文章对你有用,请随意赞赏