web-dev-qa-db-ja.com

C ++の強く型付けされたtypedef

厳密に型指定されたtypedefを宣言して、コンパイル段階で特定のクラスのバグをキャッチする方法を考えています。多くの場合、intをいくつかのタイプのID、または位置または速度のベクトルにtypedefします。

typedef int EntityID;
typedef int ModelID;
typedef Vector3 Position;
typedef Vector3 Velocity;

これはコードの意図をより明確にすることができますが、長い夜のコーディングの後、異なる種類のIDを比較したり、速度に位置を追加したりするなど、愚かな間違いをする可能性があります。

EntityID eID;
ModelID mID;

if ( eID == mID ) // <- Compiler sees nothing wrong
{ /*bug*/ }


Position p;
Velocity v;

Position newP = p + v; // bug, meant p + v*s but compiler sees nothing wrong

残念ながら、強く型付けされたtypedefに対して私が見つけた提案には、ブーストの使用が含まれます。これは、少なくとも私にとっては不可能です(少なくともc ++ 11は持っています)。だから少し考えて、私はこのアイデアに出会い、誰かがそれを実行したいと思いました。

まず、基本型をテンプレートとして宣言します。ただし、テンプレートパラメーターは定義内では何にも使用されません。

template < typename T >
class IDType
{
    unsigned int m_id;

    public:
        IDType( unsigned int const& i_id ): m_id {i_id} {};
        friend bool operator==<T>( IDType<T> const& i_lhs, IDType<T> const& i_rhs );
};

フレンド関数は、実際にはクラス定義の前に前方宣言する必要があり、テンプレートクラスの前方宣言が必要です。

次に、基本型のすべてのメンバーを定義します。これは、テンプレートクラスであることを思い出すだけです。

最後に、それを使用したい場合は、次のようにtypedefします。

class EntityT;
typedef IDType<EntityT> EntityID;
class ModelT;
typedef IDType<ModelT> ModelID;

タイプは完全に分離されました。 EntityIDを受け取る関数は、たとえば、代わりにModelIDを渡そうとすると、コンパイラエラーをスローします。基本タイプをテンプレートとして宣言する必要があることとは別に、それに伴う問題もあり、かなりコンパクトです。

私は誰かがこのアイデアについてコメントや批評を持っていることを望んでいましたか?

これを書いているときに頭に浮かんだ問題の1つは、たとえば位置や速度の場合、以前のようにタイプ間を自由に変換できないことです。ベクトルにスカラーを掛ける前に別のベクトルが得られるので、次のようにします。

typedef float Time;
typedef Vector3 Position;
typedef Vector3 Velocity;

Time t = 1.0f;
Position p = { 0.0f };
Velocity v = { 1.0f, 0.0f, 0.0f };

Position newP = p + v*t;

強く型付けされたtypedefを使用して、速度を時間でマルチタイプ化すると位置が得られることをコンパイラーに通知する必要があります。

class TimeT;
typedef Float<TimeT> Time;
class PositionT;
typedef Vector3<PositionT> Position;
class VelocityT;
typedef Vector3<VelocityT> Velocity;

Time t = 1.0f;
Position p = { 0.0f };
Velocity v = { 1.0f, 0.0f, 0.0f };

Position newP = p + v*t; // Compiler error

これを解決するには、すべての変換を明示的に特化する必要があると思います。一方、この制限は他の種類のエラーを防ぐのに役立ちます(たとえば、速度に距離を掛けると、おそらくこのドメインでは意味がありません)。だから私は引き裂かれ、私の元の問題、またはそれを解決するための私のアプローチについて人々が意見を持っているかどうか疑問に思っています。

52
Kian

これらはファントムタイプのパラメーターです。つまり、パラメーター化されたタイプのパラメーターであり、それらの表現には使用されませんが、同じ表現を持つタイプの異なる「スペース」を分離するために使用されます。

スペースといえば、それはファントムタイプの便利なアプリケーションです。

template<typename Space>
struct Point { double x, y; };

struct WorldSpace;
struct ScreenSpace;

// Conversions between coordinate spaces are explicit.
Point<ScreenSpace> project(Point<WorldSpace> p, const Camera& c) { … }

ただし、ご覧のとおり、ユニットタイプにはいくつかの問題があります。実行できることの1つは、基本コンポーネントの整数指数のベクトルにユニットを分解することです。

template<typename T, int Meters, int Seconds>
struct Unit {
  Unit(const T& value) : value(value) {}
  T value;
};

template<typename T, int MA, int MB, int SA, int SB>
Unit<T, MA - MB, SA - SB>
operator/(const Unit<T, MA, SA>& a, const Unit<T, MB, SB>& b) {
  return a.value / b.value;
}

Unit<double, 0, 0> one(1);
Unit<double, 1, 0> one_meter(1);
Unit<double, 0, 1> one_second(1);

// Unit<double, 1, -1>
auto one_meter_per_second = one_meter / one_second;

ここでは、ファントム値を使用して、ランタイム値に関連するユニットの指数に関するコンパイル時の情報をタグ付けしています。これは、速度や距離などを個別に構造化するよりも拡張性が高く、ユースケースをカバーするのに十分な場合があります。

41
Jon Purdy

私はいくつかの整数値の異なる意味を区別し、それらの間の暗黙の変換を禁止したいという同様のケースがありました。私はこのような一般的なクラスを書きました:

template <typename T, typename Meaning>
struct Explicit
{
  //! Default constructor does not initialize the value.
  Explicit()
  { }

  //! Construction from a fundamental value.
  Explicit(T value)
    : value(value)
  { }

  //! Implicit conversion back to the fundamental data type.
  inline operator T () const { return value; }

  //! The actual fundamental value.
  T value;
};

もちろん、さらに安全にしたい場合は、Tコンストラクターexplicitも作成できます。次に、Meaningは次のように使用されます。

typedef Explicit<int, struct EntityIDTag> EntityID;
typedef Explicit<int, struct ModelIDTag> ModelID;
7
mindriot

以下が製品コードでどのように機能するかはわかりませんが(私はCS101初心者のようなC++ /プログラミング初心者です)、C++のマクロsysを使用してこれを作り上げました。

#define newtype(type_, type_alias) struct type_alias { \

/* make a new struct type with one value field
of a specified type (could be another struct with appropriate `=` operator*/

    type_ inner_public_field_thing; \  // the masked_value
    \
    explicit type_alias( type_ new_value ) { \  // the casting through a constructor
    // not sure how this'll work when casting non-const values
    // (like `type_alias(variable)` as opposed to `type_alias(bare_value)`
        inner_public_field_thing = new_value; } }
1
Noein