【问题标题】:Succinctly rewrite a set of functions with variable number of arguments简洁地重写一组具有可变数量参数的函数
【发布时间】:2014-10-06 13:42:13
【问题描述】:

我正在尝试找出一种更简洁的方式来编写这段相当丑陋的代码:

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()
    { }
};

【问题讨论】:

  • 我真诚地希望唯一的原因是因为作者最初没有可用的 C++11 功能。如果情况确实如此,并且上述问题已经得到解决,那么确实可以将其中的大部分内容都废弃为可变参数解决方案。
  • 替代可变参数的解决方案,您可以使用std::initializer_list<Object>

标签: c++ variadic-templates variadic


【解决方案1】:

以下是使用可变参数模板的方法:

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 );
}

可变参数模板的问题在于,您不能限制它们使用“特定类型的可变参数数量”。您可以保持原样(并从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
    {}
};

【讨论】:

  • @RichardHodges 差不多。打败你一分钟:-)
  • 感谢您的两个回答 :) 我如何整理 TupleN 类,它目前遭受同样丑陋的重复?
  • @Pi 更新了答案
【解决方案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 传递给工作人员

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));
}

因为如果需要,右值引用将绑定到左值引用,因此完全允许将 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 因为这个构造完美地保留了传递的内容。如果你传递一个引用,它会一直作为引用传递。如果您传递一个对象,它将作为 r 值传递,直到使用它的最后一刻。

如果您想了解更多信息,请在 Google 上搜索“完美转发”并准备好让您大吃一惊:-)

【讨论】:

  • 我开始看到了!你能解释一下双&&吗?
  • @Pi 更新了答案。如果您想了解更多信息,请与我联系。
猜你喜欢
  • 2014-11-13
  • 2011-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多