型のコンストラクターの引数を推測する方法はありません。
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 は、コンストラクターのアドレスを取得してはならないことを示しています。したがって、識別子を提供する方法はありません。したがって、控除は発生しません。
控除が不可能なため、別の解決策を検討する価値があるかもしれません。各ソリューションには独自の長所と短所がありますが、それらのすべてでは、引数の型をコンストラクターの外のコンテキストで明示的にリストする必要があります。
- コンストラクターの引数の型を関数テンプレートに提供します。
- 長所:
- 短所:
- 型とその型のコンストラクターの引数の型との間の関連付けはあまりわかりません。
- 関連付けは再利用できません。たとえば、関連付けを複数の関数テンプレートまたはクラス テンプレートに渡す場合は、関連付けを複製する必要があります。
- アリティが 1 以上のコンストラクタを持つすべての型に必要です。
- 型特性との関連付けを維持します。
- 長所:
- 再利用可能。
- 型とその型のコンストラクターの引数の型との間の関連付けがより明確になります。
- 過度に複雑ではありません。
- 短所:
- コーディングは、関連付けを関数テンプレートに直接提供するよりも少し複雑です。
- アリティが 1 以上のコンストラクタを持つすべての型に必要です。
- タイプごとにファクトリー関数を作成します。
- 長所:
- とてもシンプルです。
- 再利用可能。
- 型とその型のコンストラクターの引数の型との間の関連付けは非常に明確です。
- 短所:
- アリティが 0 の場合でも、すべてのタイプに必要です。これは、スコープによるあいまいさがなくなるため、侵襲的なファクトリ関数で軽減できます。非侵襲的なファクトリ関数の場合、署名に衝突が発生する可能性があるため、関数名は一意である必要があります。
- メタプログラミングを多用して、コンストラクターの引数の型のベクトルを設定します。テンプレート コードは、実行可能な一致を識別しようとして、増大するリストを反復処理します。
- 長所:
- 型に同様のコンストラクターがある場合、ベクター内の 1 つのエントリが複数の型の実行可能な一致として機能する可能性があります。
- 短所:
- はるかに複雑です。
- テンプレートの深さをサポートするためにコンパイラ引数を変更する必要がある場合があります。
ご使用の環境で説明されている状況を考えると、次のようになります。
- 多くのクラスがあります。
- 一部はすでに存在し、変更すべきではありません。
- これらの多くは日常的に書かれています。
- コンストラクターは一意です。
C++ 仕様で因数分解すると、小林丸を定義したと思います。環境に適応できるアプローチを決定するには、長所と短所を比較検討する必要があります。最も単純なアプローチは、より多くの型が作成されたときにコードを変更するために必要な場所が 1 つだけであるため、既に用意されているものである可能性があります。
それにもかかわらず、非侵襲的な方法で型のコンストラクターに関する情報を提供する型特性を使用するアプローチを次に示します。可変個引数テンプレートなどの 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 つの引数を受け入れるように特殊化されています。これは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