pybind11 repr
最近在使用 pybind11 做 C++ 接口导出 Python 绑定的工作,发现一个比较诡异的问题,当使用 pybind11/stl_bind.h 对 std::vector
或 std::map
这两种容器进行绑定时,如果容器的成员类型是 std::string
、int
等类型,自定义的 __repr__
接口总是无法生效,查看 __repr__
接口可以看到 __repr__
有重载实现:
| __repr__(...)
| __repr__(*args, **kwargs)
| Overloaded function.
|
| 1. __repr__(self: x.MapString2String) -> str
|
| Return the canonical string representation of this map.
|
| 2. __repr__(self: x.MapString2String) -> str
通过进一步阅读代码,发现是 pybind11/stl_bind.h 自作主张,当容器成员支持 operator<<
操作时,会自动插入 __repr__
实现(如下代码以 bind_map
为例,bind_vector
类似):
/* Fallback functions */
template <typename, typename, typename... Args> void map_if_insertion_operator(const Args &...) { }
template <typename Map, typename Class_> auto map_if_insertion_operator(Class_ &cl, std::string const &name)
-> decltype(std::declval<std::ostream&>() << std::declval<typename Map::key_type>() << std::declval<typename Map::mapped_type>(), void()) {
cl.def("__repr__",
[name](Map &m) {
std::ostringstream s;
s << name << '{';
bool f = false;
for (auto const &kv : m) {
if (f)
s << ", ";
s << kv.first << ": " << kv.second;
f = true;
}
s << '}';
return s.str();
},
"Return the canonical string representation of this map."
);
}
template <typename Map, typename holder_type = std::unique_ptr<Map>, typename... Args>
class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args&&... args) {
...
// Register stream insertion operator (if possible)
detail::map_if_insertion_operator<Map, Class_>(cl, name);
...
}
很不幸,由于 map_if_insertion_operator
没有考虑重载或局部特化的情况,导致没有办法扩展 pybind11::detail
名称空间以定义其重载或局部特化的实现,因此暂时我们只能被动接受这个内置的 __repr__
实现。
C++ 函数模板局部特化
C++ 并不支持函数模板的局部特化,如果有必要,则需要特殊的处理,可以参考下面为 map_if_insertion_operator
增加局部特化支持的例子:
#include <iostream>
#include <map>
#include <string>
class C {};
using MapString2String = std::map<std::string, std::string>;
template<typename Map, typename Class_>
struct map_repr_impl {
static void _(Class_& cl, const std::string& name) {
std::cout << "generic template\n";
}
};
template<typename Class_>
struct map_repr_impl<MapString2String, Class_> {
static void _(Class_& cl, const std::string& name) {
std::cout << "partial specialization\n";
}
};
template <typename, typename, typename... Args> void map_if_insertion_operator(const Args &...) { }
template <typename Map, typename Class_>
auto map_if_insertion_operator(Class_& cl, const std::string& name)
-> decltype(std::declval<std::ostream&>() << std::declval<typename Map::key_type>() << std::declval<typename Map::mapped_type>(), void()) {
return map_repr_impl<Map, Class_>::_(cl, name);
}
int main() {
C i;
std::string name{"abc"};
map_if_insertion_operator<std::map<std::string, int>, C>(i, name);
map_if_insertion_operator<std::map<std::string, std::string>, C>(i, name);
return 0;
}
参考资料
An introduction to C++’s SFINAE concept: compile-time introspection of a class member
https://jguegant.github.io/blogs/tech/sfinae-introduction.html
How to Make SFINAE Pretty
https://www.fluentcpp.com/2018/05/15/make-sfinae-pretty-1-what-value-sfinae-brings-to-code/
Choose between different implementations depending on type properties at compile time, in C++
Notes on C++ SFINAE
https://www.bfilipek.com/2016/02/notes-on-c-sfinae.html
SFINAE decltype comma operator trick
http://skebanga.blogspot.com/2013/03/sfinae-decltype-comma-operator-trick.html
Function Templates Partial Specialization in C++
https://www.fluentcpp.com/2017/08/15/function-templates-partial-specialization-cpp/
Template Partial Specialization In C++
https://www.fluentcpp.com/2017/08/11/how-to-do-partial-template-specialization-in-c/
Controlling overload resolution
https://foonathan.net/blog/2015/10/16/overload-resolution-1.html
Modern C++ Features – decltype and std::declval
https://arne-mertz.de/2017/01/decltype-declval/
C++11 模板元编程
https://www.jianshu.com/p/b56d59f77d53
最后修改于 2019-08-03