Add a function to be exposed. *func* is expected to be a
        :class:`cgen.FunctionBody`.

        Because a function can have several signatures exported,
        this method actually creates a wrapper for each specialization
        and a global wrapper that checks the argument types and
        runs the correct candidate, if any

def add_function_to(self, to, func, name, ctypes, signature):
        """
        Add a function to be exposed. *func* is expected to be a
        :class:`cgen.FunctionBody`.

        Because a function can have several signatures exported,
        this method actually creates a wrapper for each specialization
        and a global wrapper that checks the argument types and
        runs the correct candidate, if any
        """
        to.append(func)

        args_unboxing = []  # turns PyObject to c++ object
        args_checks = []  # check if the above conversion is valid
        wrapper_name = pythran_ward + 'wrap_' + func.fdecl.name

        for i, t in enumerate(ctypes):
            args_unboxing.append('from_python<{}>(args_obj[{}])'.format(t, i))
            args_checks.append('is_convertible<{}>(args_obj[{}])'.format(t, i))
        if ctypes:
            arg_decls = func.fdecl.arg_decls[:len(ctypes)]
            keywords = ",".join('"{}"'.format(arg.name) for arg in arg_decls)
            wrapper = dedent('''
                static PyObject *
                {wname}(PyObject *self, PyObject *args, PyObject *kw)
                {{
                    PyObject* args_obj[{size}+1];
                    char const* keywords[] = {{{keywords}, nullptr}};
                    if(! PyArg_ParseTupleAndKeywords(args, kw, "{fmt}",
                                                     (char**)keywords, {objs}))
                        return nullptr;
                    if({checks})
                        return to_python({name}({args}));
                    else {{
                        return nullptr;
                    }}
                }}''')
        else:
            keywords = ''
            wrapper = dedent('''
                static PyObject *
                {wname}(PyObject *self, PyObject *args, PyObject *kw)
                {{
                    return to_python({name}({args}));
                }}''')

        self.wrappers.append(
            wrapper.format(name=func.fdecl.name,
                           size=len(ctypes),
                           fmt="O" * len(ctypes),
                           objs=', '.join('&args_obj[%d]' % i
                                          for i in range(len(ctypes))),
                           args=', '.join(args_unboxing),
                           checks=' && '.join(args_checks),
                           wname=wrapper_name,
                           keywords=keywords,
                           )
        )

        func_descriptor = wrapper_name, ctypes, signature
        self.functions.setdefault(name, []).append(func_descriptor)