无法推断类型的构造函数的参数。
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();
};
将使用表示构造函数特征的模板。我使用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