2015-12-15 10 views
20

私たちのプロジェクトでは、表現する変数が何であるかを明示的に述べるためにかなりの "使用"を使用します。これは、主にstd::string識別番号PortalIdまたはCakeIdのように使用されます。今現在できることはC++とtypedefを使って強く型付けする

using PortalId = std::string; 
using CakeId = std::string; 

PortalId portal_id("2"); 
CakeId cake_id("is a lie"); 

portal_id = cake_id; // OK 

です。私たちは好きではありません。 yum yumメソッドのほとんどを元のオブジェクトから保護しながら、リンゴとオレンジを混ぜないようにコンパイル時に型チェックをしたいと思います。

これは、C++での使用が次のものに近くなるように、割り当てが失敗し、マップやその他のものでそれを使用できるということですか?

SAFE_TYPEDEF(std::string, PortalId); 
SAFE_TYPEDEF(std::string, CakeId); 

int main() 
{ 
    PortalId portal_id("2"); 
    CakeId cake_id("is a lie"); 
    std::map<CakeId, PortalId> p_to_cake; // OK 

    p_to_cake[cake_id] = portal_id; // OK 
    p_to_cake[portal_id] = cake_id; // COMPILER ERROR 

    portal_id = cake_id;  // COMPILER ERROR 
    portal_id = "1.0";   // COMPILER ERROR 
    portal_id = PortalId("42"); // OK 
    return 0; 

} 

我々はすでに、テンプレートと組み合わせて、いくつかのマクロを試してみましたが、私たちが必要な、非常に何を取得できませんでした。追加するには、C++ 14を使用することができます。

EDIT:我々が思いついたコードは醜い あると動作しません

#define SAFE_TYPEDEF(Base, name) \ 
class name : public Base { \ 
public: \ 
    template <class... Args> \ 
    explicit name (Args... args) : Base(args...) {} \ 
    const Base& raw() const { return *this; } \ 
}; 

ました。そして、それは動作しません私はコンパイラが portal_id = cake_id; と大丈夫だったことを意味します。

EDIT2:explicitというキーワードが追加されました。このコードでは、コードが実際にうまく動作します。これが正しい方法であるかどうか、それがすべての不幸な状況をカバーするかどうかは確かです。

+0

まあ何も。 – Jendas

+1

c-torの前に明示的に追加するだけです。 – firescreamer

+0

これを文字列に対して行うと、その型を使用すると簡単に未定義の動作につながります: 'SAFE_TYPEDEF(std :: string、S); std :: string * s = new S(); delete s; '' std :: string'はベースクラスとして使用するためのものではありません。 – Jens

答えて

13

ここであなたがやりたいだろう最小限の完全なソリューションです。

さらに多くの演算子などを追加して、クラスをより便利にすることができます。

#include <iostream> 
#include <string> 
#include <map> 

// define some tags to create uniqueness 
struct portal_tag {}; 
struct cake_tag {}; 

// a string-like identifier that is typed on a tag type 
template<class Tag> 
struct string_id 
{ 
    // needs to be default-constuctable because of use in map[] below 
    string_id(std::string s) : _value(std::move(s)) {} 
    string_id() : _value() {} 

    // provide access to the underlying string value   
    const std::string& value() const { return _value; } 
private: 
    std::string _value; 

    // will only compare against same type of id. 
    friend bool operator < (const string_id& l, const string_id& r) { 
     return l._value < r._value; 
    } 
}; 


// create some type aliases for ease of use  
using PortalId = string_id<portal_tag>; 
using CakeId = string_id<cake_tag>; 

using namespace std; 

// confirm that requirements are met 
auto main() -> int 
{ 
    PortalId portal_id("2"); 
    CakeId cake_id("is a lie"); 
    std::map<CakeId, PortalId> p_to_cake; // OK 

    p_to_cake[cake_id] = portal_id; // OK 
// p_to_cake[portal_id] = cake_id; // COMPILER ERROR 

// portal_id = cake_id;  // COMPILER ERROR 
// portal_id = "1.0";   // COMPILER ERROR 
    portal_id = PortalId("42"); // OK 
    return 0; 
} 

もここなど

あなたは、私がstringに変換する演算子を提供していないことに注意しますをostreamするために、ストリーミング、ハッシュマップを処理し、更新されたバージョンです。これは慎重です。 to_stringのオーバーロードを提供することによって、このクラスのユーザーが文字列として使用する意図を明示するように要求しています。

#include <iostream> 
#include <string> 
#include <map> 
#include <unordered_map> 

// define some tags to create uniqueness 
struct portal_tag {}; 
struct cake_tag {}; 

// a string-like identifier that is typed on a tag type 
template<class Tag> 
struct string_id 
{ 
    using tag_type = Tag; 

    // needs to be default-constuctable because of use in map[] below 
    string_id(std::string s) : _value(std::move(s)) {} 
    string_id() : _value() {} 

    // provide access to the underlying string value 
    const std::string& value() const { return _value; } 
private: 
    std::string _value; 

    // will only compare against same type of id. 
    friend bool operator < (const string_id& l, const string_id& r) { 
     return l._value < r._value; 
    } 

    friend bool operator == (const string_id& l, const string_id& r) { 
     return l._value == r._value; 
    } 

    // and let's go ahead and provide expected free functions 
    friend 
    auto to_string(const string_id& r) 
    -> const std::string& 
    { 
     return r._value; 
    } 

    friend 
    auto operator << (std::ostream& os, const string_id& sid) 
    -> std::ostream& 
    { 
     return os << sid.value(); 
    } 

    friend 
    std::size_t hash_code(const string_id& sid) 
    { 
     std::size_t seed = typeid(tag_type).hash_code(); 
     seed ^= std::hash<std::string>()(sid._value); 
     return seed; 
    } 

}; 

// let's make it hashable 

namespace std { 
    template<class Tag> 
    struct hash<string_id<Tag>> 
    { 
     using argument_type = string_id<Tag>; 
     using result_type = std::size_t; 

     result_type operator()(const argument_type& arg) const { 
      return hash_code(arg); 
     } 
    }; 
} 


// create some type aliases for ease of use 
using PortalId = string_id<portal_tag>; 
using CakeId = string_id<cake_tag>; 

using namespace std; 

// confirm that requirements are met 
auto main() -> int 
{ 
    PortalId portal_id("2"); 
    CakeId cake_id("is a lie"); 
    std::map<CakeId, PortalId> p_to_cake; // OK 

    p_to_cake[cake_id] = portal_id; // OK 
    // p_to_cake[portal_id] = cake_id; // COMPILER ERROR 

    // portal_id = cake_id;  // COMPILER ERROR 
    // portal_id = "1.0";   // COMPILER ERROR 
    portal_id = PortalId("42"); // OK 

    // extra checks 

    std::unordered_map<CakeId, PortalId> hashed_ptocake; 
    hashed_ptocake.emplace(CakeId("foo"), PortalId("bar")); 
    hashed_ptocake.emplace(CakeId("baz"), PortalId("bar2")); 

    for(const auto& entry : hashed_ptocake) { 
     cout << entry.first << " = " << entry.second << '\n'; 

     // exercise string conversion 
     auto s = to_string(entry.first) + " maps to " + to_string(entry.second); 
     cout << s << '\n'; 
    } 

    // if I really want to copy the values of dissimilar types I can express it: 

    const CakeId cake1("a cake ident"); 
    auto convert = PortalId(to_string(cake1)); 

    cout << "this portal is called '" << convert << "', just like the cake called '" << cake1 << "'\n"; 


    return 0; 
} 
+0

優秀なアイデア教科書。 – Elyasin

+1

あなたの解決策は素晴らしいですが、私は '_value'の型に2番目のテンプレートパラメータを追加します:http://ideone.com/IVlefk – dkg

+0

さらに良い、はい。次に、独自のIDの自動生成などをカバーするようにvalue_traits クラスのスペシャライゼーションを作成することができます。 –

1

これを行う標準的な方法があればうれしいですが、現在はありません。将来何かが標準化されるかもしれません:Opaque Typedefsには、関数エイリアスとより豊かな継承構造でこれをしようとする論文と、Named Typesにあるものがあります。これは強力なtypedefを導入するための単一の新しいキーワードあなたはそれを呼びたいと思う。

ブーストシリアライズライブラリは、あなたに必要なものを与えるかもしれないBOOST_STRONG_TYPEDEFを提供します。

SAFE_TYPEDEFの代替品があります。は他のブースト依存関係がなく、わずかに変更されているため、typedefタイプから割り当てることはできません。私はまた、移動コンストラクタと代入演算子を追加し、defaultを利用した:

namespace detail { 
    template <typename T> class empty_base {}; 
} 

template <class T, class U, class B = ::detail::empty_base<T> > 
struct less_than_comparable2 : B 
{ 
    friend bool operator<=(const T& x, const U& y) { return !(x > y); } 
    friend bool operator>=(const T& x, const U& y) { return !(x < y); } 
    friend bool operator>(const U& x, const T& y) { return y < x; } 
    friend bool operator<(const U& x, const T& y) { return y > x; } 
    friend bool operator<=(const U& x, const T& y) { return !(y < x); } 
    friend bool operator>=(const U& x, const T& y) { return !(y > x); } 
}; 

template <class T, class B = ::detail::empty_base<T> > 
struct less_than_comparable1 : B 
{ 
    friend bool operator>(const T& x, const T& y) { return y < x; } 
    friend bool operator<=(const T& x, const T& y) { return !(y < x); } 
    friend bool operator>=(const T& x, const T& y) { return !(x < y); } 
}; 

template <class T, class U, class B = ::detail::empty_base<T> > 
struct equality_comparable2 : B 
{ 
    friend bool operator==(const U& y, const T& x) { return x == y; } 
    friend bool operator!=(const U& y, const T& x) { return !(x == y); } 
    friend bool operator!=(const T& y, const U& x) { return !(y == x); } 
}; 

template <class T, class B = ::detail::empty_base<T> > 
struct equality_comparable1 : B 
{ 
    friend bool operator!=(const T& x, const T& y) { return !(x == y); } 
}; 

template <class T, class U, class B = ::detail::empty_base<T> > 
struct totally_ordered2 
    : less_than_comparable2<T, U 
    , equality_comparable2<T, U, B 
     > > {}; 

template <class T, class B = ::detail::empty_base<T> > 
struct totally_ordered1 
    : less_than_comparable1<T 
    , equality_comparable1<T, B 
     > > {}; 

#define SAFE_TYPEDEF(T, D)          \ 
struct D              \ 
    : totally_ordered1< D          \ 
    , totally_ordered2< D, T         \ 
    > >               \ 
{                \ 
    T t;              \ 
    explicit D(const T& t_) : t(t_) {};       \ 
    explicit D(T&& t_) : t(std::move(t_)) {};     \ 
    D() = default;            \ 
    D(const D & t_) = default;         \ 
    D(D&&) = default;           \ 
    D & operator=(const D & rhs) = default;      \ 
    D & operator=(D&&) = default;        \ 
    operator T &() { return t; }        \ 
    bool operator==(const D & rhs) const { return t == rhs.t; } \ 
    bool operator<(const D & rhs) const { return t < rhs.t; } \ 
}; 

Live Demo

+0

ああ、私が思い出せなかった言葉だった名前のタイプ。 – Jendas

+0

また、名前付きの型はまだC++ 14で導入されていないことには非常に驚いています。 – Jendas

+0

downvotesを推測するのは、私が完全なソリューションを提供しなかったためです。今そこに1つがあります。 – TartanLlama

9

これまでのところ、ここで、過度に複雑なように思え提供ソリューションは、私の試みではありません:私は、コードを提供します:-D

#include <string> 

enum string_id {PORTAL, CAKE}; 

template <int ID> class safe_str : public std::string { 
    public: 
    using std::string::string; 
}; 

using PortalId = safe_str<PORTAL>; 
using CakeId = safe_str<CAKE>; 
+0

このアプローチの問題は、異なるタイプのsafe_strが共に文字列から派生していることです。したがって、彼らはお互いに変換可能です。つまり、safe_strは実際には安全ではありません。 –

+1

@RichardHodgesあなたはそれらを変換することができますが、私はあなたが偶然それをやる方法はありません。 – kamikaze

+1

あなたは正しいです。文字列のコンストラクタは明示的です。ニース。 :) –

関連する問題