からページを盗みたいと思うでしょうstd::begin
。
CRTP は優れていますが、c を持つという要件を処理するには、すべての構造自体を変更する必要があります。実際、c のコードはあなたの問題であり、供給されているデータの問題ではありません。
当然のことながら、CRTP とこのアプローチの両方が成功するゼロ オーバーヘッドが必要になります。
代わりに、条件付き.c()
で呼び出すか.a()+.b()
、その存在に応じて呼び出します。以下に 2 つのアプローチを示します。
フリー関数を作成しますc
:
template<class T, class...Ignored>
decltype(auto) c(T& t, Ignored&&...)
次の 2 つの実装にディスパッチします。
{
auto which = has_c_method<T>;
return details::c(which{}, t);
}
Wherehas_c_method
は、渡された型に.c()
メソッドがあるかどうかを検出する traits bool 型です。(下に書いておきます)。
名前空間の詳細:
namespace details{
template<class T>
auto c(std::false_type, T&){
return t.a()-t.b();
}
template<class T>
auto c(std::true_type, T&){
return t.c();
}
}
そして私たちは元気です。c(t)
また、t
s 名前空間に自由な非可変関数がある場合は、それが優先されることに注意してください(それが機能しますIgnored
)。
その特性クラスを作成する必要がありますが、多くの SO 回答がそれをカバーしています。
c
推奨されるよりも適切な名前。;)
この設計には、ターゲットの型を書いている人々にアクションへの参加を強制しないという利点があります。が定義されているかどうかに応じて、t.c()
またはのいずれかにアクセスするだけです。t.a()+t.b()
t.c()
これで、さらに一般的な方向からアプローチできるようになりました。代わりに、ディスパッチする関数を作成しませんc
...
コンパイル時のブランチを書きます:
namespace details {
template<bool>
struct branch {
template<class T, class F_true, class F_false>
std::result_of_t<F_true(T)> operator()( T&&t, F_true&&f, F_false&&){
return decltype(f)(f)(decltype(t)(t));
}
};
template<>
struct branch<false> {
template<class T, class F_true, class F_false>
std::result_of_t<F_false(T)> branch( T&& t, F_true&&, F_false&&f){
return decltype(f)(f)(decltype(t)(t));
}
};
}
template<template<class...>class Z, class T, class F_true, class F_false>
auto branch( T&& t, F_true&& f_true, F_false&& f_false )
-> decltype( details::branch<Z<T>{}>{}(std::declval<T>(), std::declval<F_true>(), std::declval<F_false>() )
{
return details::branch<Z<T>{}>{}(decltype(t)(t), decltype(f_true)(f_true), decltype(f_false)(f_false) );
}
偽の場合はありません:
template<template<class...>class Z, class T, class F_true>
void branch( T&& t, F_true&& f_true )
{
branch( std::forward<T>(t), std::forward<F_true>(f_true), [](auto&&){} );
}
使用する:
int c = branch<has_c_method>(
t,
[&](auto& t){ return t.c(); },
[&](auto& t){ return t.a()-t.b(); }
);
これにより、これをもう少しアドホックに行うことができます。
branch<template>( arg, if_true, if_false )
template
タイプ (の r/l 値修飾を含む) で評価しarg
ます。結果の型のインスタンスが constexpr コンテキスト内で true を返す場合、if_true
が実行されます。constexpr コンテスト内で false を返した場合、if_false
実行されます。
どちらの場合も、arg
選択したラムダに渡されます。
C++14 からのラムダ サポートと合わせてauto
、これにより、比較的簡潔に条件付きでコンパイルされるコードを記述できます。
実行されていないラムダは、インスタンス化されていない単なるテンプレートです。run ラムダは、arg のインスタンスでインスタンス化されます。したがって、実行されていないラムダには、選択されていない場合に有効なコードが含まれている必要はありません。
のタイプは、branch
実際には 2 つのオプションの間で静的に選択されます。実際にはさまざまな型を返すことができます。変換は行われません。
if_false のない return のオーバーbranch
ロードvoid
。
has_c_method
これは、ほとんどが一般的なコードで書かれたスケッチです。
namespace details {
template<template<class...>class Z, class, class...Ts>
struct can_apply_helper:
std::false_type
{};
template<template<class...>class Z, class...Ts>
struct can_apply_helper<Z, std::void_t<Z<Ts...>>, Ts...>:
std::true_type
{};
}
// is true_type iff Z<Ts...> is valid:
template<template<class...>class Z, class...Ts>
using can_apply = typename details::can_apply_helper<Z, void, Ts...>::type;
// return type of t.c(args...). Easy to write
// and with the above, makes has_c_method a one-liner:
template<class T, class...Args>
using c_method_result = decltype(std::declval<T>().c(std::declval<Args>()...));
template<class T, class...Args>
using has_c_method = can_apply<c_method_result, T, Args...>;
に非常によく似can_apply
たものを追加するという提案がありstd
ます。
上記の私の非慣用的な使用に注意してくださいdecltype(x)(x)
。std::forward<X>(x)
これは、 が転送参照であるコンテキストと同等であり、パラメータ ラムダX
内でも機能します。「宣言された型にauto&&
キャストする」ことを意味します。が値 (非参照) 型である場合は、それが複製x
されることに注意してください(これは、転送を優先する理由であり、決してそれを行いません)。ただし、これは、上記の使用のいずれにも当てはまりません。x
decltype(x)(x)