Simple types with a higher value (C ++ 11)

I often come across situations (in my C ++ / C ++ 11 code) where I have a type that basically behaves like an inline type (or a simple simple type like std::string ), but it matters for 32-bit number or a bunch of characters. I did not find anything useful on the Internet, because in fact I do not know what conditions to look for ...

Examples:

  • I once worked on a system where the elements were identified by an identifier. And these identifiers were std::string (probably not a good idea in the first place, but that's a different story). Which was really bad, although there was a fact that these identifiers were passed through the system as std::string or as const char* s. Therefore, it was difficult (impossible) to say where in the identifiers of the base code base were used when searching for the type. The names of the variables were all variants of the ID (ID, id, Id) or key, or just me or the name or something else. So you could not search by name. Therefore, I would prefer to pass these variables of type id_t .
  • Network ports: they are uint16_t s. But I would like to pass them as network_port_t s.

I usually used typedefs to make things a little nicer. However, this approach has several problems:

  • You do not need to use typedef. You can pass variables by type "raw" (for example, std::string instead of id_t ).
  • If the raw type is a template, you do this with forward declaring a typedef (e.g. with shared_ptr).
  • Forward ad typedef is a maintenance issue. If the original type changes, you can change things everywhere.

Another thing I tried with the network port example was writing a thin shell class with operator uint16_t . This solved the problem with forward declarations. But then I ran into a trap with some logging macros that used printf inside. Printfs still worked (well, compiled), but did not print the port number, but (I think) the address of the object.

I calculated with dimensions such as weight or length. Boost.Units may be worth the look (even if it seems a little "heavy"). But for the two examples above, this is not suitable.

What is the best practice to achieve what I want (using Boost is an option)?

In short: What I want to achieve is to pass "types with a higher value" as my own type, and not as a simple raw / low level / non-abstract type. (View) as having a user-defined type. It is advisable, without huge overhead, to write a complete class for each type with almost identical implementations, only to be able to do what the built-in can do.

+5
source share
3 answers

To create an integer that is not an integer (or a string, not a string) and cannot advertise or downgrade it), you can only create a new type that simply means "write a new class." There is no way - at least on the base type - to inherit behavior without smoothing. A new_type<int> has no arithmetic (unless you define it).

But you can define

 template<class Innertype, class Tag> class new_type { Innertype m; public: template<class... A> explicit new_type(A&&... a) :m(std::forward<A>(a)...) {} const Innertype& as_native() const { return m; } }; 

and complete the entire workout only once for everyone.

 template<class T, class I> auto make_new_type(I&& i) { return new_type<I,T>(std::forward<I>(i)); } template<class A, class B, class T> auto operator+(const new_type<A,T>& a, const new_type<B,T>& b) { return make_new_type<T>(a.as_native()+b.as_native()); } .... 

and then

 struct ID_tag; typedef new_type<std::string,ID_tag> ID; struct OtehrID_tag; typedef new_type<std::string,OtehrID_tag> OtherID; 

and ID and OtherID cannot be mixed in expressions.

 NOTE: 

indefinite return auto-function is standard for C ++ 14, but GCC accepts it in C ++ 11 as-well.

+2
source

1. Strong Typedefs

You can use BOOST_STRONG_TYPEDEF to get convenience.

It uses macros, and I think you are doing heterogeneous comparisons (for example id == "123" ).

There are two versions, be sure to take one of the Boost utilities.

2.flavoured_string <>

For strings, you can trick the system using scented strings (inventor: R. Martinho Fernandez).

This leads to the fact that you can actually change the characteristics to std::basic_string and create virtually different tagged aliases:

 #include <string> #include <iostream> namespace dessert { template <typename Tag> struct not_quite_the_same_traits : std::char_traits<char> {}; template <typename Tag> using strong_string_alias = std::basic_string<char, not_quite_the_same_traits<Tag>>; using vanilla_string = std::string; using strawberry_string = strong_string_alias<struct strawberry>; using caramel_string = strong_string_alias<struct caramel>; using chocolate_string = strong_string_alias<struct chocolate>; template <typename T> struct special; template <typename T> using special_string = strong_string_alias<special<T>>; std::ostream& operator<<(std::ostream& os, vanilla_string const& s) { return os << "vanilla: " << s.data(); } std::ostream& operator<<(std::ostream& os, strawberry_string const& s) { return os << "strawberry: " << s.data(); } std::ostream& operator<<(std::ostream& os, caramel_string const& s) { return os << "caramel: " << s.data(); } std::ostream& operator<<(std::ostream& os, chocolate_string const& s) { return os << "chocolate: " << s.data(); } template <typename T> std::ostream& operator<<(std::ostream& os, special_string<T> const& s) { return os << "special: " << s.data(); } } int main() { dessert::vanilla_string vanilla = "foo"; dessert::strawberry_string strawberry = "foo"; dessert::caramel_string caramel = "foo"; dessert::chocolate_string chocolate = "foo"; std::cout << vanilla << '\n'; std::cout << strawberry << '\n'; std::cout << caramel << '\n'; std::cout << chocolate << '\n'; dessert::special_string<struct nuts> nuts = "foo"; std::cout << nuts << '\n'; } 
+6
source
  template <typename tag_t, typename value_t> struct meaningful_value { typedef value_t value_type; meaningful_value() : value() {} explicit meaningful_value(const value_type & val) : value(val) {} operator const value_type & () const { return value; } protected: value_type value; }; typedef meaningful_value<struct ParamType1_tag, double> ParamType1; typedef meaningful_value<struct ParamType2_tag, double> ParamType2; 

This is basically what boost :: quantity does, but allows default construction; tag structure can be declared inplace in typedef; therefore, declaring a new parameter type is a single-line transaction; you can choose whether you want a macro for it

(Edited to correct constructor name)

+1
source

Source: https://habr.com/ru/post/1203394/


All Articles