强类型使用和typedef [英] Strongly typed using and typedef

查看:76
本文介绍了强类型使用和typedef的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我们的项目中,我们使用了很多用法来明确说明变量应该表示的内容。它主要用于 std :: string 标识符,例如 PortalId CakeId 。现在我们当前可以做的是

 ,使用PortalId = std :: string; 
使用CakeId = std :: string;

PortalId portal_id( 2);
CakeId cake_id(撒谎);

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(撒谎);
std :: map< CakeId,PortalId> p_to_cake; // OK

p_to_cake [cake_id] = portal_id; // OK
p_to_cake [portal_id] = cake_id; //编译器错误

portal_id = cake_id; //编译器错误
portal_id = 1.0; //编译器错误
portal_id = PortalId( 42); // OK
返回0;

}

我们已经尝试过将宏与模板结合使用,但是没有不能完全满足我们的需求。并添加-我们可以使用c ++ 17。



编辑:我们想到的代码是

  #define SAFE_TYPEDEF(Base,name)\ 
类名:public Base {\
public:\
模板< class ... Args> explicit
显式名称(Args ... args):Base(args ...){} \
const Base& raw()const {return * this; } \
};

这很难看,不能使用。而且通过它不起作用,我的意思是编译器可以使用 portal_id = cake_id;



EDIT2::添加了 explicit 关键字,通过该关键字,我们的代码实际上可以很好地适用于我们的示例。不确定这是否是正确的方法,以及它是否涵盖了所有不幸的情况。

解决方案

这是一个最小的完整解决方案,将执行您想要的操作。



您可以添加更多运算符等,以使该类在您认为合适的情况下更有用。

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

//定义一些标签以创建唯一性
struct portal_tag {};
struct cake_tag {};

//在标签类型
template< class Tag>上键入的类似字符串的标识符
struct string_id
{
//必须是默认可构造的,因为在
以下的map []中使用string_id(std :: string s):_value(std :: move (s)){}
string_id():_value(){}

//提供对基础字符串值
的访问const std :: string& value()const {return _value; }
私人:
std :: string _value;

//将仅与相同类型的ID进行比较。
朋友布尔运算符< (const string_id& l,const string_id& r){
return l._value< r._value;
}
};


//使用PortalId = string_id< portal_tag>创建一些类型别名以便于使用
;
使用CakeId = string_id< cake_tag> ;;

使用命名空间std;

//确认满足要求
auto main()-> int
{
PortalId portal_id( 2);
CakeId cake_id(撒谎);
std :: map< CakeId,PortalId> p_to_cake; // OK

p_to_cake [cake_id] = portal_id; // OK
// p_to_cake [portal_id] = cake_id; //编译器错误

// portal_id = cake_id; //编译器错误
// portal_id = 1.0; //编译器错误
portal_id = PortalId( 42); // OK
返回0;
}

这是一个更新的版本,还处理哈希映射,流到ostream等。 / p>

您会注意到,我没有提供用于转换为 string 的运算符。这是故意的。我要求此类的用户通过提供 to_string 的重载来明确表达使用它作为字符串的意图。

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

//定义一些标签以创建唯一性
struct portal_tag {};
struct cake_tag {};

//在标签类型
template< class Tag>上键入的类似字符串的标识符
struct string_id
{
using tag_type = Tag;

//必须是默认可构造的,因为在
以下的map []中使用string_id(std :: string s):_value(std :: move(s)){}
string_id():_value(){}

//提供对基础字符串值
的访问const std :: string& value()const {return _value; }
私人:
std :: string _value;

//将仅与相同类型的ID进行比较。
朋友布尔运算符< (const string_id& l,const string_id& r){
return l._value< r._value;
}

朋友布尔运算符==(const string_id& l,const string_id& r){
return l._value == r._value;
}

//,然后继续提供预期的免费功能
朋友
auto to_string(const string_id& r)
-> const std :: string&
{
返回r._value;
}

朋友
自动操作符<< (std :: ostream& os,const string_id& sid)
-> std :: ostream&
{
return os<< sid.value();
}

朋友
std :: size_t hash_code(const string_id& sid)
{
std :: size_t seed = typeid(tag_type).hash_code ();
种子^ = std :: hash< std :: string>()(sid._value);
回报种子;
}

};

// //将其设为可散列的

名称空间std {
template< class Tag>
struct hash< string_id< Tag>>
{
using arguments_type = string_id< Tag> ;;
使用result_type = std :: size_t;

result_type运算子()(const arguments_type& arg)const {
return hash_code(arg);
}
};
}


//使用PortalId = string_id< portal_tag>创建一些类型别名,以便于使用

使用CakeId = string_id< cake_tag> ;;

使用命名空间std;

//确认满足要求
auto main()-> int
{
PortalId portal_id( 2);
CakeId cake_id(撒谎);
std :: map< CakeId,PortalId> p_to_cake; // OK

p_to_cake [cake_id] = portal_id; // OK
// p_to_cake [portal_id] = cake_id; //编译器错误

// portal_id = cake_id; //编译器错误
// portal_id = 1.0; //编译器错误
portal_id = PortalId( 42); // OK

//额外的支票

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’;

//练习字符串转换
auto s = to_string(entry.first)+映射为 + to_string(entry.second);;
cout<< s<< ‘n’;
}

//如果我真的想复制不同类型的值,我可以表达它:

const CakeId cake1( a ake ident);
自动转换= PortalId(to_string(cake1));

cout<< 此门户称为‘<<转换<< ,就像蛋糕上的一样。<< cake1<< ’\n;


返回0;
}


In our project, we use quite a lot of "usings" to explicitly state what is variable supposed to represent. It is mainly used for std::string identificators like PortalId or CakeId. Now what we currently can do is

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

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

portal_id = cake_id; // OK

which we don't like. We would like to have type check during compile time to prevent us from mixing apples and oranges while preserving most of the yum yum methods from the original object.

So the question is - can this be done in C++ such that the usage would be close to what follows, assignments would fail and we could still use it with, say, maps and other containers?

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;

}

We have already tried macros in combination with templates but didn't quite get what we needed. And to add - we CAN use c++17.

EDIT: The code we came up with was

#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; } \
};

which is ugly and doesn't work. And by it doesn't work I mean that compiler was ok with portal_id = cake_id;.

EDIT2: Added explicit keyword, with which our code actually works nicely for our example. Not sure though whether this is the right way to go and whether it covers all unfortunate situations.

解决方案

Here's a minimal complete solution that will do what you want.

You can add more operators etc to make the class more useful as you see fit.

#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;
}

here's an updated version that also handles hash maps, streaming to ostream etc.

You will note that I have not provided an operator to convert to string. This is deliberate. I am requiring that users of this class explicitly express the intent to use it as a string by providing an overload of 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;
}

这篇关于强类型使用和typedef的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆