简洁地使用可变数量的参数重写一组函数

时间:2014-10-06 13:42:13

标签: c++ variadic-templates variadic

我正试图找出一种更清晰的方式来编写这个相当丑陋的代码:

class PythonExtensionBase : public PyObject
{
:
public:
    // helper functions to call function fn_name with 0 to 9 args
    Object callOnSelf( const std::string &fn_name );
    Object callOnSelf( const std::string &fn_name, const Object &arg1 );
    Object callOnSelf( const std::string &fn_name, const Object &arg1, const Object &arg2 );
    Object callOnSelf( const std::string &fn_name, const Object &arg1, const Object &arg2, const Object &arg3 );
    :

(一直到9)

这些功能在相应的.cxx中实现:

Py::Object PythonExtensionBase::callOnSelf( const std::string &fn_name )
{
    Py::TupleN args;
    return  self().callMemberFunction( fn_name, args );
}

Py::Object PythonExtensionBase::callOnSelf( const std::string &fn_name,
                                            const Py::Object &arg1 )
{
    Py::TupleN args( arg1 );
    return  self().callMemberFunction( fn_name, args );
}

Py::Object PythonExtensionBase::callOnSelf( const std::string &fn_name,
                                            const Py::Object &arg1, const Py::Object &arg2 )
{
    Py::TupleN args( arg1, arg2 );
    return self().callMemberFunction( fn_name, args );
}

Py::Object PythonExtensionBase::callOnSelf( const std::string &fn_name,
                                            const Py::Object &arg1, const Py::Object &arg2, const Py::Object &arg3 )
{
    Py::TupleN args( arg1, arg2, arg3 );
    return self().callMemberFunction( fn_name, args );
}
:

有效的任务是概括:

X( A a, B b1, B b2, B b3 ) {
    foo( b1, b2, b3 );
}

我可以看到一个可变参数模板可能是要走的路,但我很难理解如何使用它。

TupleN类的定义如下:

class TupleN: public Tuple
{
public:
    TupleN()
    : Tuple( 0 )
    {
    }

    TupleN( const Object &obj1 )
    : Tuple( 1 )
    {
        setItem( 0, obj1 );
    }

    TupleN( const Object &obj1, const Object &obj2 )
    : Tuple( 2 )
    {
        setItem( 0, obj1 );
        setItem( 1, obj2 );
    }

    TupleN( const Object &obj1, const Object &obj2, const Object &obj3 )
    : Tuple( 3 )
    {
        setItem( 0, obj1 );
        setItem( 1, obj2 );
        setItem( 2, obj3 );
    }

    :

    virtual ~TupleN()
    { }
};

2 个答案:

答案 0 :(得分:4)

以下是您如何使用可变参数模板:

template <class... Arg>
Object callOnSelf( const std::string &fn_name, Arg&&... arg )
{
    Py::TupleN args(std::forward<Arg>(arg)...);
    return  self().callMemberFunction( fn_name, args );
}

可变参数模板的问题在于,您不能将它们限制为使用&#34;特定类型的可变数量的参数。&#34; 您可以将其保留原样(并从TupleN构造函数中获取编译错误),或者您可以使用静态断言和帮助程序稍微帮助它:

template <class Car, class... Cdr>
struct isObject
{
  static constexpr bool value = isObject<Car> && isObject<Cdr...>::value;
};

template <class T>
struct isObject<T>
{
  static constexpr bool value = std::is_convertible<const T&, const Py::Object&>::value;
};

template <class... Arg>
Object callOnSelf( const std::string &fn_name, Arg&&... arg )
{
    static_assert(isObject<Arg...>::value, "All arguments to callOnSelf must be PyObject compatible");
    Py::TupleN args(std::forward<Arg>(arg)...);
    return  self().callMemberFunction( fn_name, args );
}

至于TupleN类,你可以做一个类似的技巧:

class TupleN: public Tuple
{
public:
    template <class Arg...>
    TupleN(Arg&&... arg)
    : Tuple( sizeof...(arg))
    {
      setItems(0, std::forward<Arg>(arg)...);
    }

private:
    template <class Car, class... Cdr>
    void setItems(size_t idx, Car&& car, Cdr&&... cdr) {
      setItem(idx, std::forward<Car>(car));
      setItems(idx + 1, std::forward<Cdr>(cdr)...);
    }

    void setItems(size_t)  // recursion terminator
    {}
};

答案 1 :(得分:2)

你想要的是一个可变参数模板参数。

template<class...Args>
Py::Object PythonExtensionBase::callOnSelf( const std::string &fn_name,
                                            Args&&...args_ )
{
    Py::TupleN args( std::forward<Args>(args_)... );
    return self().callMemberFunction( fn_name, args );
}

更新:但为什么要将args_作为右值参考传递?

回答:完美转发。

考虑:

struct X { }; // an expensive to copy object

foo(X {});  // call with a temporary 

假设foo将其与其他内部函数的论证交换出来

void foo(X x)  // copied
{
  inner_foo(x);
}

并且inner_foo进一步将X传递给worker

void inner_foo(X x) // copied
{
  really_inner_foo(x); // copied again
}

你想要避免所有这些副本,对吧? X甚至可能是一种不可复制的类型。

在传递X的特定情况下你写这个的方式是:

void foo(X x) {
  inner_foo(std::move(x));
}

你可以提高效率(完全避免任何动作):

template<class X_LIKE>
void foo(X_LIKE&& x) {
  inner_foo(std::forward<X_LIKE>(x));
}

因为如果需要,r值引用将绑定到l值引用,因此将const ref传递给X是完全允许的:

const X x;
foo(x);
然后foo有效地成为:

void foo(const X& x) {
  inner_foo(x); // calls the const X& version of inner_foo
}

因此,在通用模板形式中,我们通过r值引用并使用std :: forward&lt;&gt;因为这个结构完美地保留了传递的内容。如果您传递引用,它将作为引用一直传递。如果你传递一个对象,它将作为一个r值传递,直到它被使用的最后一刻。

如果您想了解更多信息,可以在谷歌搜索“完美转发”并准备好让您的思绪大打折扣: - )