可以使用以下方法“剥离”函数参数类型:

void foo_double(double a)
{
}

void foo_int(int a)
{

}

template <class R, class A0>
void bar(R (*fp)(A0))
{
    // Do something related with A0
}

int main()
{
    bar(foo_double); // A0 is double
    bar(foo_int);    // A0 is int
}
是否可以对类构造函数执行相同的“参数类型剥离”?
编辑:
我相信我没有在原始代码片段中清楚地解释自己。这是完整的方案。
我有多个类C1,...,Cn,需要将它们作为函数公开给python。假设所有类都有一个共同的void Run()方法。但是,这些类的构造函数接受不同的参数。要将函数公开给python,我使用boost.python,它会在处理所有类型转换(主要是基元)时自动将函数导出到适当的python函数。
我的第一个解决方案是:
class C1
{
public:
    C1() {}
    void Run();
};

class C2
{
public:
    C2(double a) {}
    void Run();
};

template <class T>
void python_wrapper()
{
    T instance();
    instance.Run();
}

template <class T, class A0>
void python_wrapper(A0 a0)
{
    T instance(a0);
    instance.Run();
}

BOOST_PYTHON_MODULE(_pythonmodule)
{
    // This is boost.python stuff
    python::def("f1", python_wrapper<C1>);
    python::def("f2", python_wrapper<C2, double>);
}
而且...有效。
我现在要完成的工作是在推断构造函数参数类型时使用python_wrapper<C2>而不是python_wrapper<C2, double>
正如我在原始帖子中所示。如果我包装函数而不是类,则可以完成类似的操作。

最佳答案

无法推断类型的构造函数的参数。

C++ 98/03和C++ 11规范明确列出了可能发生类型推导的上下文(请参见第14.8.2节及其子节)。演绎可以提高模板编程的寿命,不是强制性的。可以通过演绎完成的所有操作也可以通过显式调用来实现。因此,为了使推演成为可能,将要求人们可以明确地向函数模板提供构造函数。

但是,这是不可能的。如C++ 98/03和C++ 11规范的第12.1节所述,构造函数没有名称。此外,C++ 98/03的12.1.12节和C++ 11的12.1.10节规定,不得使用构造函数的地址。因此,无法提供标识符。因此,不可能发生扣除。

由于不可能进行扣除,因此可能值得考虑其他解决方案。每个解决方案都有自己的优缺点集,但是所有这些解决方案都需要在构造函数之外的某些上下文中明确列出参数类型:

  • 将构造函数的参数类型提供给函数模板。
  • 优点:
  • 相当简单。
  • 缺点:
  • 类型与类型的构造函数的参数类型之间的关联不是很明显。
  • 关联不可重用。例如,如果将关联传递给多个功能模板或类模板,则需要复制该关联。
  • 具有Arity为1或更大的构造函数的每种类型均需要。
  • 维护具有类型特征的关联。
  • 优点:
  • 可重用。
  • 类型与类型的构造函数的参数类型之间的关联更明显。
  • 不太复杂。
  • 缺点:
  • 编码比直接将关联提供给功能模板要复杂得多。
  • 具有Arity为1或更大的构造函数的每种类型均需要。
  • 为每种类型创建一个工厂函数。
  • 优点:
  • 非常简单。
  • 可重用。
  • 类型与类型的构造函数的参数类型之间的关联非常明显。
  • 缺点:
  • 即使arity为0,每种类型都需要。可以通过具有侵入性的工厂函数来缓解这种情况,因为范围不会造成歧义。对于非侵入性工厂功能,签名上可能存在冲突,因此功能名称必须唯一。
  • 大量使用元编程来获得构造函数参数类型的 vector 。然后,模板代码将遍历不断增长的列表,以尝试确定可行的匹配项。
  • 优点:
  • 如果类型具有相似的构造函数,则 vector 中的单个条目可作为多种类型的可行匹配。
  • 缺点:
  • 更复杂。
  • 可能需要修改编译器参数以支持模板深度。

  • 考虑到针对您的环境描述的情况:
  • 有很多类。
  • 一些已经存在,因此不应更改。
  • 每天都会编写更多此类文章。
  • 构造函数是唯一的。

  • 考虑到C++规范,我相信我们已经定义了Kobayashi Maru。您将需要权衡利弊,以确定哪种方法可以适合您的环境。最简单的方法可能已经是您已经使用的方法,因为随着创建更多类型,它只需要一个位置即可更改代码。

    不过,这是一种使用类型特征的方法,该方法以非侵入方式提供有关类型构造函数的信息。如果没有C++ 11功能(如可变参数模板),则会有一些样板代码。此外,该实现可能无法涵盖所有​​情况,例如多个构造函数。

    使用原始问题中显示的类:
    class C1
    {
    public:
      C1();
      void Run();
    };
    
    class C2
    {
    public:
      C2(double a);
      void Run();
    };
    

    将使用代表构造函数特征的模板。我正在使用Boost.MPL提供的类型列表来表示构造函数的参数类型。默认的constructor_traits指示不需要任何参数。
    /// @brief constructor_traits is a type_trait that is used to noninvasively
    ///        associated T with constructor meta information, such as T'
    ///        constructor's argument types.
    ///
    ///        For example, if Foo has a constructor that accepts a single
    ///        integer, then constructor_traits<Foo>::type should be
    ///        boost::mpl::vector<int>.
    template <typename T>
    struct constructor_traits
    {
      typedef boost::mpl::vector<> type;
    };
    

    然后,此特征专门用于带有接受参数的构造函数的类型,例如C2
    /// Specialize constructor_traits for C2 to indicate that its constructor
    /// accepts a double.
    template <>
    struct constructor_traits<C2>
    {
      typedef boost::mpl::vector<double> type;
    };
    

    boost::mpl::vector 是代表构造函数参数的类型列表。它通过 boost::mpl::at 提供随机访问。为了提供对元素的更简洁的访问,引入了一个辅助类型:
    /// @brief Helper type that makes getting the constructor argument slightly
    ///        easier.
    template <typename Vector,
              std::size_t Index>
    struct get_constructor_arg
      : boost::mpl::at<Vector, boost::mpl::int_<Index> >
    {};
    

    在将函数公开给Boost.Python时,所需的语法仅提供一种类型。功能模板或类模板均可用于解决此问题。我决定使用类模板,因为它减少了一些样板代码。
    /// @brief runner type is used to provide a static run function that
    ///        will delegate the construction and running of type T based
    ///        on T's constructor_traits.
    template <typename T,
              typename Args = typename constructor_traits<T>::type,
              std::size_t = boost::mpl::size<Args>::value>
    struct runner
    {
      static void run()
      {
        T().Run();
      }
    };
    

    然后,此模板专门用于构造函数接受的参数数量。下面专门接受一个参数。这由特化模板参数列表中的1确定。
    /// Specialization for runner for types with have a single argument
    /// constructor.
    template <typename T,
              typename Args>
    struct runner<T, Args, 1>
    {
      static void run(typename get_constructor_arg<Args, 0>::type a0)
      {
        T(a0).Run();
      }
    };
    

    功能模板也可以用于解决此问题。我决定使用类模板是因为:
  • 不需要SFINAE。需要使用enable_if构造来选择正确的模板。
  • 能够为类模板提供默认模板参数,从而避免了多次获取constructor_trait的需要。

  • 生成的Boost.Python调用看起来像:
    BOOST_PYTHON_MODULE(_pythonmodule)
    {
      boost::python::def("f1", &runner<C1>::run);
      boost::python::def("f2", &runner<C2>::run);
    }
    

    这是完整的代码:
    #include <iostream>
    #include <boost/mpl/vector.hpp>
    #include <boost/python.hpp>
    
    class C1
    {
    public:
      C1() {}
      void Run() { std::cout << "run c1" << std::endl; }
    };
    
    class C2
    {
    public:
      C2(double a) : a_(a) {}
      void Run() { std::cout << "run c2: " << a_ << std::endl;}
    private:
      double a_;
    };
    
    /// @brief constructor_traits is a type_trait that is used to noninvasively
    ///        associated T with constructor meta information, such as T'
    ///        constructor's argument types.
    ///
    ///        For example, if Foo has a constructor that accepts a single
    ///        integer, then constructor_traits<Foo>::type should be
    ///        boost::mpl::vector<int>.
    template <typename T>
    struct constructor_traits
    {
      typedef boost::mpl::vector<> type;
    };
    
    /// Specialize constructor_traits for C2 to indicate that its constructor
    /// accepts a double.
    template <>
    struct constructor_traits<C2>
    {
      typedef boost::mpl::vector<double> type;
    };
    
    /// @brief Helper type that makes getting the constructor argument slightly
    ///        easier.
    template <typename Vector,
              std::size_t Index>
    struct get_constructor_arg
      : boost::mpl::at<Vector, boost::mpl::int_<Index> >
    {};
    
    /// @brief runner type is used to provide a static run function that
    ///        will delegate the construction and running of type T based
    ///        on T's constructor_traits.
    template <typename T,
              typename Args = typename constructor_traits<T>::type,
              std::size_t = boost::mpl::size<Args>::value>
    struct runner
    {
      static void run()
      {
        T().Run();
      }
    };
    
    /// Specialization for runner for types with have a single argument
    /// constructor.
    template <typename T,
              typename Args>
    struct runner<T, Args, 1>
    {
      static void run(typename get_constructor_arg<Args, 0>::type a0)
      {
        T(a0).Run();
      }
    };
    
    BOOST_PYTHON_MODULE(example)
    {
      boost::python::def("f1", &runner<C1>::run);
      boost::python::def("f2", &runner<C2>::run);
    }
    

    并测试输出:
    >>> import example
    >>> example.f1()
    run c1
    >>> example.f2(3.14)
    run c2: 3.14
    

    07-28 01:32
    查看更多