构造函数参数的模板参数推断

Xya*_*and 2 c++ templates boost-python template-meta-programming

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

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
}    
Run Code Online (Sandbox Code Playgroud)

是否可以对类构造函数执行相同的"参数类型剥离"?

编辑:

我相信我没有在原始代码片段中清楚地解释自己.这是完整的方案.

我有多个类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>);
}
Run Code Online (Sandbox Code Playgroud)

并且...它的工作原理.

我现在要完成的是使用python_wrapper<C2>而不是python_wrapper<C2, double>在推断构造函数参数类型时.

正如我在原帖中所展示的那样.如果我包装函数而不是类,我可以完成类似的事情.

Tan*_*ury 9

没有办法推断出类型的构造函数的参数.

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.这可以通过侵入式工厂功能来缓解,因为范围不会产生歧义.对于非侵入式工厂功能,签名可能存在冲突,因此功能名称必须是唯一的.
  • 大量使用元编程来获得构造函数参数类型的向量.然后,模板代码将迭代越来越多的列表,试图识别可行的匹配.
    • 优点:
      • 如果类型具有类似的构造函数,则向量中的单个条目可以用作多种类型的可行匹配.
    • 缺点:
      • 更复杂.
      • 可能需要修改编译器参数以支持模板深度.

鉴于您的环境描述的情况:

  • 有很多课程.
  • 有些已经存在,不应该更改.
  • 更多这些是每天写的.
  • 构造函数是独一无二的.

当考虑到C++规范时,我相信我们已经定义了Kobayashi Maru.您将不得不权衡利弊,以确定适合您的环境的方法.最简单的方法可能已经是您现有的方法,因为它只需要一个位置即可在创建更多类型时更改代码.


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

使用原始问题中提供的类:

class C1 
{
public:
  C1();
  void Run();
};

class C2
{
public:
  C2(double a);
  void Run();
};
Run Code Online (Sandbox Code Playgroud)

将使用表示构造函数特征的模板.我使用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;
};
Run Code Online (Sandbox Code Playgroud)

然后,此特征专门用于具有接受参数的构造函数的类型,例如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;
};
Run Code Online (Sandbox Code Playgroud)

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> >
{};
Run Code Online (Sandbox Code Playgroud)

将函数公开给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();
  }
};
Run Code Online (Sandbox Code Playgroud)

然后,此模板专用于构造函数接受的参数数量.以下是专门接受一个参数.这由特化的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();
  }
};
Run Code Online (Sandbox Code Playgroud)

功能模板也可用于解决此问题.我决定使用类模板,因为:

  • 不需要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);
}
Run Code Online (Sandbox Code Playgroud)

这是完整的代码:

#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);
}
Run Code Online (Sandbox Code Playgroud)

并测试输出:

>>> import example
>>> example.f1()
run c1
>>> example.f2(3.14)
run c2: 3.14
Run Code Online (Sandbox Code Playgroud)