def add_boilerplate(
    *array_params: str,
) -> Callable[[Callable[P, R]], Callable[P, R]]:
    """
    Adds required boilerplate to the wrapped cupynumeric.ndarray or
    module-level function.

    Every time the wrapped function is called, this wrapper will convert all
    specified array-like parameters to cuPyNumeric ndarrays. Additionally, any
    "out" or "where" arguments will also always be automatically converted.
    """
    to_convert = set(array_params)
    assert len(to_convert) == len(array_params)

    def decorator(func: Callable[P, R]) -> Callable[P, R]:
        assert not hasattr(
            func, "__wrapped__"
        ), "this decorator must be the innermost"

        params = signature(func).parameters
        extra = to_convert - set(params)
        assert len(extra) == 0, f"unknown parameter(s): {extra}"

        # we also always want to convert "out" and "where"
        # even if they are not explicitly specified by the user
        to_convert.update(("out", "where"))

        out_idx = -1
        indices = set()
        for idx, param in enumerate(params):
            if param == "out":
                out_idx = idx
            if param in to_convert:
                indices.add(idx)

        @wraps(func)
        def wrapper(*args: Any, **kwargs: Any) -> R:
            # convert specified non-None positional arguments, making sure
            # that any out-parameters are appropriately writeable
            converted_args = []
            for idx, arg in enumerate(args):
                if idx in indices and arg is not None:
                    if idx == out_idx:
                        arg = convert_to_cupynumeric_ndarray(arg, share=True)
                        if not arg.flags.writeable:
                            raise ValueError("out is not writeable")
                    else:
                        arg = convert_to_cupynumeric_ndarray(arg)
                converted_args.append(arg)
            args = tuple(converted_args)

            # convert specified non-None keyword arguments, making sure
            # that any out-parameters are appropriately writeable
            for k, v in kwargs.items():
                if k in to_convert and v is not None:
                    if k == "out":
                        kwargs[k] = convert_to_cupynumeric_ndarray(
                            v, share=True
                        )
                        if not kwargs[k].flags.writeable:
                            raise ValueError("out is not writeable")
                    else:
                        kwargs[k] = convert_to_cupynumeric_ndarray(v)

            if settings.doctor():
                doctor.diagnose(func.__name__, args, kwargs)
            return func(*args, **kwargs)

        return wrapper

    return decorator

    Adds required boilerplate to the wrapped cupynumeric.ndarray or
    module-level function.

    Every time the wrapped function is called, this wrapper will convert all
    specified array-like parameters to cuPyNumeric ndarrays. Additionally, any
    "out" or "where" arguments will also always be automatically converted.
    
cupynumeric/_array/util.py