1

テンプレートへのパラメーターとして使用される一連のクラスがあります。それらはすべて、いくつかの非公式のインターフェース (別名概念) に準拠しています。

template <typename T>
int func( T& t ) { return t.a() + t.b() + t.c(); }

Fooこの例では、パラメータとしてまたはを使用してテンプレートをインスタンス化するとします。そのため、メソッドおよび をBar実装する必要があります。a bc

struct Foo { int a(); int b(); int c(); };
struct Bar { int a(); int b(); int c(); };

今、私はこれらのクラスをたくさん持っており、他の関数に関して、関数の 1 つのデフォルトの実装を持ちたいと考えています。

たとえば、デフォルトで とcの差を返したいa()b()します。したがって、このコードをすべてのクラスにコピーする必要なく、 andを定義a()して自動的に実装するだけで十分だと思います。b()c()int c() { return a()- b();}

以前はポリモーフィズム (のデフォルト (仮想) 実装を持つ基本クラスの純粋な仮想関数としてa()とを定義することにより) を使用してこの結果を達成していましたが、パフォーマンス上の理由からこのメカニズムから離れました。b()c()

テンプレート パラメーター クラスを使用してこの種の結果を得る (つまり、既定の実装を 1 回記述する) ための推奨される解決策があるかどうかを知りたいです。

4

4 に答える 4

9

からページを盗みたいと思うでしょう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)また、ts 名前空間に自由な非可変関数がある場合は、それが優先されることに注意してください(それが機能します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されることに注意してください(これは、転送を優先する理由であり、決してそれを行いません)。ただし、これは、上記の使用のいずれにも当てはまりません。xdecltype(x)(x)

于 2016-01-14T19:15:16.823 に答える
2

Kerrek のコメントに触発されたバージョンですが、std::true_typestd::false_type

#include <iostream>
#include <type_traits>

struct Foo {
    int a() { return 10; }
    int b() { return 20; }
    int c() { return 30; }
};

struct Bar {
    int a() { return 8; }
    int b() { return 3; }
};

template<typename T, typename = void>
struct has_c : std::false_type {
    static int call(T t) { return t.a() - t.b(); }
};

template<typename T>
struct has_c<T, decltype(std::declval<T>().c(), void())> : std::true_type {
    static int call(T t) { return t.c(); }    
};

template <typename T>
int f(T&& t) {
    return has_c<T>::call(std::forward<T>(t));
}

int main()
{
    Foo foo;
    Bar bar;

    std::cout << f(foo) << "\n";
    std::cout << f(bar) << "\n";

    return 0;
}

コリルでライブ

于 2016-01-14T19:18:29.607 に答える
2

CRTP を使用して、それを継承するクラスにデフォルトの実装を提供するのはどうでしょうか。

template <typename Child>
class DefaultC
{
public:
    int c() { Child& this_obj = static_cast<Child&>(*this); return this_obj.a()- this_obj.b();}
};

それで:

struct Foo : public DefaultC<Foo> { int a(); int b(); };

(また、関数が変更されていない場合は、const とマークしてください)

于 2016-01-14T18:55:40.347 に答える
2

最初にCRTPを試します:

template < typename Derived >
struct subtract
{
    int c() const
    {
        auto this_ = static_cast<Derived const*>(this);
        return this_->a() - this_->b();
    }
};

struct whatnot : subtract<whatnot>
{
    int a() const { return 42; }
    int b() const { return 666; }
};
于 2016-01-14T18:56:34.060 に答える