2016-10-03 4 views
1

は私のようなテンプレートクラスを持っていると仮定しますクラスは、等価演算子を定義していないにもかかわらず:仮想関数やジェネリックプログラミング

int main(int argc, char* argv[]) 
{ 
    A a; 
    Foo<A> foo(a); 

    return 0; 
} 

しかし、私ははFooを作る場合は::バー()仮想:

virtual bool Bar(const T& value) 
    { 
     return m_Value == value; 
    } 

コードはもはやコンパイル:

エラーC2676:バイナリ '==': 'A' は、この演算子を定義していないがまたは定義済みのオペレータが受け入れ可能なタイプへの変換

これはなぜ問題なのかを完全に理解しています。私が間違っている場合は私を修正しますが、私の理解は、関数が仮想であるため、Fooのvテーブルでそれを参照できるように、関数をコンパイルする必要があります(呼び出されなくても)。

この問題を回避する方法があるのでしょうか。部分的なインターフェイスだけを実装できるジェネリック型を扱うテンプレートクラスを用意したいと思います。欠落しているビットが使用されていない限り、コードは正常にコンパイルする必要があります。これは、STDコンテナの多くがすでにどのように動作しているかに似ていますが、仮想関数は使用していません。

どうすればよいですか?これに優雅な解決策はありますか?

+1

関数が仮想の場合は、関数を定義してコンパイルする必要があります。これは基本的なことです。 –

+3

クラステンプレートの非仮想メンバ関数は、それ自体が関数テンプレートであり、最初に使用されたときにのみインスタンス化されます。対照的に、仮想メンバ関数は、クラステンプレートの特殊化インスタンス化の一部として常にインスタンス化されます。 –

+1

クラステンプレートがインスタンス化されるとき、メンバー関数の*宣言のみがコンパイルされ、ボディは宣言されません。また、仮想非純粋メンバ関数は常にodr-usedであり、クラステンプレートインスタンス化の一部として完全にコンパイルされます。 – 0x499602D2

答えて

1

上記のKerrek SBで説明したように、仮想関数は、テンプレートがインスタンス化されたときに常にインスタンス化されます。したがって、仮想メソッドを使用しないときにプログラムを正しくコンパイルする方法はありませんラップしたいクラスが使用されていて、コンパイルに失敗した場合はoperator==を返します。

しかし、実行時に(assert/terminate)プログラムをクラッシュさせたり、例外をスローすることはできます。

免責事項:提供すると主張するインターフェイスをサポートしていないクラスを作成することができますので、あなたがやろうとしていることをするのは良い考えではないと思います。自己責任で以下を使用してください。

ここに行く方法は、ラップされたクラスがそれ自体を実装していない場合でも、指定したいすべてのメソッドに対してカスタムタイプの特性を使用することです。この上のあなたのケースでは唯一のoperator==あり、対応するコードは次のようになります。

namespace traits { 
template <typename T> 
using operator_eq_t = decltype(std::declval<T>() == std::declval<T>()); 

template <typename, typename = void> 
struct has_operator_eq : std::false_type {}; 

// check that operator== is defined and returns the correct type `bool`. 
template <typename T> 
struct has_operator_eq<T, std::void_t<operator_eq_t<T>>> 
    : std::is_same<operator_eq_t<T>, bool> {}; 
} // namespace traits 

あなたがC++ 1Zへのアクセス権を持っていない場合は、まだあなたがstd::void_tの独自のバージョンを作ることができ、他のすべてがあります有効なC++ 14:それと

template <typename...> 
using void_t = void 

代わりにあなたがタグ派遣であなたのラッパークラステンプレートを作成することができます

template <typename T> 
class Foo : public IFoo<T> { 
public: 
    explicit Foo(T const& value) 
     : m_Value(value) { 
    } 

    bool Bar(T const& value) override { 
     return BarImpl(value, traits::has_operator_eq<T>{}); 
    } 

private: 
    T m_Value; 

    bool BarImpl(T const& value, std::false_type) { 
     // some sensible default, in this case you might 
     // consider just to return false 
     assert(!"Called `Bar` on class that does not implement `operator==`."); 
     throw std::logic_error("Called `Bar` on class that does not implement `operator==`."); 
    } 

    bool BarImpl(T const& value, std::true_type) { 
     return value == m_Value; 
    } 
}; 

作業EXを十分なものはhereです。

+0

これは非常に興味深いです。しかし、私がコメントで議論したように、クラスデザインの変更はよりエレガントな解決策であると私は思います。それにもかかわらず、これは将来便利になるでしょう。 – Zeenobit