runsisi's

technical notes

C++ function template partial specialization

2019-08-03 runsisicpp

pybind11 repr

最近在使用 pybind11 做 C++ 接口导出 Python 绑定的工作,发现一个比较诡异的问题,当使用 pybind11/stl_bind.h 对 std::vectorstd::map 这两种容器进行绑定时,如果容器的成员类型是 std::stringint 等类型,自定义的 __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++

https://medium.com/@mortificador/choose-between-different-implementations-depending-on-type-properties-at-compile-time-in-c-68e3fd5cd2f8

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