次のような(おそらく追加の)テンプレート引数を使用して、アルゴリズムを拡張できます。
enum class algorithm_type
{
type_a,
type_b,
type_c
};
template <algorithm_type AlgorithmType>
void foo(int usual, double args)
{
std::cout << "common code" << std::endl;
if (AlgorithmType == algorithm_type::type_a)
{
std::cout << "doing type a..." << usual << ", " << args << std::endl;
}
else if (AlgorithmType == algorithm_type::type_b)
{
std::cout << "doing type b..." << usual << ", " << args << std::endl;
}
else if (AlgorithmType == algorithm_type::type_c)
{
std::cout << "doing type c..." << usual << ", " << args << std::endl;
}
std::cout << "more common code" << std::endl;
}
これで、次のテンプレート引数を使用して動作を選択できます。
foo<algorithm_type::type_a>(11, 0.1605);
foo<algorithm_type::type_b>(11, 0.1605);
foo<algorithm_type::type_c>(11, 0.1605);
型は定数式であるため、コンパイル時に決定されたブランチが生成されます(つまり、他のブランチはデッドコードであり、削除されていることがわかっています)。実際、コンパイラはこれについて警告する必要があります(これをどのように処理するかはあなた次第です)。
ただし、ランタイム値を正常にディスパッチすることはできます。
#include <stdexcept>
void foo_with_runtime_switch(algorithm_type algorithmType,
int usual, double args)
{
switch (algorithmType)
{
case algorithm_type::type_a:
return foo<algorithm_type::type_a>(usual, args);
case algorithm_type::type_b:
return foo<algorithm_type::type_b>(usual, args);
case algorithm_type::type_c:
return foo<algorithm_type::type_c>(usual, args);
default:
throw std::runtime_error("wat");
}
}
foo_with_runtime_switch(algorithm_type::type_a, 11, 0.1605);
foo_with_runtime_switch(algorithm_type::type_b, 11, 0.1605);
foo_with_runtime_switch(algorithm_type::type_c, 11, 0.1605);
アルゴリズムの内部は同じままで(デッドブランチが排除され、同じ最適化)、そこに到達する方法が変更されました。(このスイッチが自動的に生成されるように列挙型のアイデアを一般化することが可能であることに注意してください。少数のバリエーションがある場合は、これを学ぶとよいでしょう。)
#define
そしてもちろん、デフォルトとして特定のアルゴリズムを使用することもできます。
#define FOO_ALGORITHM algorithm_type::type_a
void foo_with_define(int usual, double args)
{
return foo<FOO_ALGORITHM>(usual, args);
}
foo_with_define(11, 0.1605);
これらすべてを一緒に使用すると、繰り返しなしで3つすべての利点が得られます。
実際には、3つすべてをオーバーロードとして使用できます。1つはコンパイル時に使用するアルゴリズムを知っているユーザー、実行時にそれを選択する必要があるユーザー、およびデフォルト(プロジェクトを介してオーバーライドできます)が必要なユーザー向けです。ワイド#define
):
// foo.hpp
enum class algorithm_type
{
type_a,
type_b,
type_c
};
// for those who know which algorithm to use
template <algorithm_type AlgorithmType>
void foo(int usual, double args)
{
std::cout << "common code" << std::endl;
if (AlgorithmType == algorithm_type::type_a)
{
std::cout << "doing type a..." << usual << ", " << args << std::endl;
}
else if (AlgorithmType == algorithm_type::type_b)
{
std::cout << "doing type b..." << usual << ", " << args << std::endl;
}
else if (AlgorithmType == algorithm_type::type_c)
{
std::cout << "doing type c..." << usual << ", " << args << std::endl;
}
std::cout << "more common code" << std::endl;
}
// for those who will know at runtime
void foo(algorithm_type algorithmType, int usual, double args)
{
switch (algorithmType)
{
case algorithm_type::type_a:
return foo<algorithm_type::type_a>(usual, args);
case algorithm_type::type_b:
return foo<algorithm_type::type_b>(usual, args);
case algorithm_type::type_c:
return foo<algorithm_type::type_c>(usual, args);
default:
throw std::runtime_error("wat");
}
}
#ifndef FOO_ALGORITHM
// chosen to be the best default by profiling
#define FOO_ALGORITHM algorithm_type::type_b
#endif
// for those who just want a good default
void foo(int usual, double args)
{
return foo<FOO_ALGORITHM>(usual, args);
}
もちろん、一部の実装タイプが常に他のタイプよりも悪い場合は、それを取り除きます。しかし、2つの有用な実装があることがわかった場合、この方法で両方を維持しても害はありません。