質問

いくつかの算術をカプセル化するクラスがあります。たとえば、固定小数点計算です。算術演算子をオーバーロードするというアイデアが好きなので、次のように書きます。

class CFixed
{
   CFixed( int   );
   CFixed( float );
};

CFixed operator* ( const CFixed& a, const CFixed& b )
{ ... }

すべて動作します。 3 * CFixed(0)およびCFixed(3)* 10.0fと書くことができます。しかし、今では、整数オペランドを使用してoperator *をより効果的に実装できることに気付きました。だから私はそれをオーバーロードします:

CFixed operator* ( const CFixed& a, int b )
{ ... }
CFixed operator* ( int a, const CFixed& b )
{ ... }

それでも動作しますが、現在CFixed(0)* 10.0fはオーバーロードバージョンを呼び出して、floatをintに変換します(floatをCFixedに変換すると予想しました)。もちろん、フロートバージョンもオーバーロードできますが、コードの組み合わせが爆発的に増えているように見えます。回避策はありますか(またはクラスを間違って設計していますか)? intでのみoperator *のオーバーロードバージョンを呼び出すようにコンパイラに指示するにはどうすればよいですか?

役に立ちましたか?

解決

int だけでなく、すべての整数型に特化したバージョンを選択したい場合は、テンプレート関数として提供し、Boost.EnableIfを使用することができますオペランドが整数型でない場合、利用可能なオーバーロードセットからこれらのオーバーロードを削除します。

#include <cstdio>
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits/is_integral.hpp>

class CFixed
{
public:
   CFixed( int   ) {}
   CFixed( float ) {}
};

CFixed operator* ( const CFixed& a, const CFixed&  )
{ puts("General CFixed * CFixed"); return a; }

template <class T>
typename boost::enable_if<boost::is_integral<T>, CFixed>::type operator* ( const CFixed& a, T  )
{ puts("CFixed * [integer type]"); return a; }

template <class T>
typename boost::enable_if<boost::is_integral<T>, CFixed>::type operator* ( T , const CFixed& b )
{ puts("[integer type] * CFixed"); return b; }


int main()
{
    CFixed(0) * 10.0f;
    5 * CFixed(20.4f);
    3.2f * CFixed(10);
    CFixed(1) * 100u;
}

当然、異なる条件を使用して、T = intの場合にのみこれらのオーバーロードを使用可能にすることもできます:typename boost::enable_if<boost::is_same<T, int>, CFixed>::type ...

クラスの設計については、おそらくテンプレートにもっと頼ることができます。たとえば、コンストラクターはテンプレートにすることができます。また、整数型と実数型を区別する必要がある場合は、この手法を使用できるようにする必要があります。

他のヒント

floatタイプでもオーバーロードする必要があります。 intからユーザー指定のタイプ(CFixed)への変換は、組み込みのdoubleへの浮動積分変換よりも優先順位が低くなります。そのため、<=>で関数を追加しない限り、コンパイラは常に<=>で関数を選択します。

詳細については、C ++ 03標準の13.3セクションを参照してください。痛みを感じます。

私もそれを見失ったようです。 :-( UncleBens は、<=>付きのバージョンを追加する必要があるため、floatを追加しても問題は解決しないと報告していますしかし、いずれにしても、組み込み型に関連するいくつかの演算子を追加するのは退屈ですが、組み合わせによるブーストは発生しません。

1つの引数のみで呼び出すことができるコンストラクタがある場合、暗黙的な変換演算子を効果的に作成しました。この例では、CFixedが必要な場合は常に、intfloatの両方を渡すことができます。これはもちろん危険です。なぜなら、関数の宣言を含めるのを忘れたとき、コンパイラーはあなたにkingえるのではなく、間違った関数を呼び出すコードを静かに生成するからです。

したがって、1つの引数だけで呼び出すことができるコンストラクターを作成するときはいつでも、良い経験則では、2つの引数を取る場合でも、この1つのfoo(int i, bool b = false)も1つの引数で呼び出すことができます、暗黙的な変換を実際に開始したい場合を除き、そのコンストラクターをexplicitにする必要があります。std::string::string(const char*)コンストラクターは、コンパイラーによって暗黙的な変換に使用されません。

クラスを次のように変更する必要があります:

class CFixed
{
   explicit CFixed( int   );
   explicit CFixed( float );
};

このルールには例外がほとんどないことがわかりました。 (<=>はかなり有名なものです。)

編集:申し訳ありませんが、<=>から<=>への暗黙的な変換を許可しないという点を逃しました。

これを防ぐ唯一の方法は、<=>の演算子も提供することです。

変換明示的についてはどうですか?

sbiに同意し、単一パラメーターコンストラクターを明示的に作成する必要があります。

演算子の爆発を避けることができます<!> lt; <!> gt;ただし、テンプレートを使用して記述する関数:

template <class T>
CFixed operator* ( const CFixed& a, T b ) 
{ ... } 

template <class T>
CFixed operator* ( T a, const CFixed& b ) 
{ ... } 

関数に含まれるコードによっては、変換をサポートする型でのみコンパイルされます。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top