Strongly Typed Using and Typedef

Strongly typed using and typedef

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

Strong typedefs

Quoting cplusplus.com,

Note that neither typedef nor using create new distinct data types.
They only create synonyms of existing types.
That means that the type
of myword above, declared with type WORD, can as well be considered of
type unsigned int; it does not really matter, since both are actually
referring to the same type.

Since int and handle are one and the same, the output 0 1 is expected.

There's a workaround though, as @interjay suggests.

You can use BOOST_STRONG_TYPEDEF.

BOOST_STRONG_TYPEDEF( int , handle );

Why aren't typedefs strongly typed?

Because C is not strongly typed and typedef has its origin in that thinking

typedef is just for convenience and readability, it doesn't create a new type.

How can I create a new primitive type using C++11 style strong typedefs?

There are several ways to solve this, but since I was looking for a fix to Bjarne's code in the presentation slides, I'm accepting this answer which @robson3.14 left in comments to the question:

#include <iostream>

template<int M, int K, int S> struct Unit { // a unit in the MKS system
enum { m=M, kg=K, s=S };
};

template<typename Unit> // a magnitude with a unit
struct Value {
double val; // the magnitude
// construct a Value from a double
constexpr explicit Value(double d) : val(d) {}
};

using Meter = Unit<1,0,0>; // unit: meter
using Second = Unit<0,0,1>; // unit: sec
using Speed = Value<Unit<1,0,-1>>; // meters/second type

// a f-p literal suffixed by ‘_s’
constexpr Value<Second> operator "" _s(long double d)
{
return Value<Second> (d);
}
// a f-p literal suffixed by ‘_m’
constexpr Value<Meter> operator "" _m(long double d)
{
return Value<Meter> (d);
}
// an integral literal suffixed by ‘_m’
constexpr Value<Meter> operator "" _m(unsigned long long d)
{
return Value<Meter> (d);
}

template<int m1, int k1, int s1, int m2, int k2, int s2>
Value<Unit<m1 - m2, k1 - k2, s1 - s2>> operator / (Value<Unit<m1, k1, s1>> a, Value<Unit<m2, k2, s2>> b)
{
return Value<Unit<m1 - m2, k1 - k2, s1 - s2>>(a.val / b.val);
}

int main()
{
Speed sp1 = 100_m / 9.8_s;
std::cout << sp1.val;
}

Strong typedef for primitive types (BOOST_STRONG_TYPEDEF is not cutting it)

  • Reading http://www.boost.org/doc/libs/1_63_0/libs/serialization/doc/strong_typedef.html
    , it seems that your example using boost compiles because the types created by boost are substitutable to the original type which are comparable.

  • Johnathan Boccara from fluentcpp provides an implementation of strong types on its github that should be what you want:

#include <cassert>
#include "NamedType/named_type.hpp"

int main() {
using TIMER_ID =
fluent::NamedType<unsigned int, struct TimerIdTag, fluent::Comparable>;
using PROCESS_ID =
fluent::NamedType<unsigned int, struct ProcessIdTag, fluent::Comparable>;

TIMER_ID a(123);
PROCESS_ID b(456);

assert(a == a);
// assert(a == b); doesn't compile
return 0;
}

use typedef for more type safe in c++

What you have doesn't help type safety at all, except perhaps as weak documentation. As far as the compiler is concerned, float, Degree, and Radian are complete synonyms; this is called a weak type alias. Strong type aliases are not a part of C++, but you can work around that. Two good articles on that are here and here. The basic idea is to create a generic class template for a strong typedef, and use that to create each individual alias.

If you don't want to write all the boilerplate yourself even once, I recommend using a third-party library to handle this. Both the authors of the posts I linked above wrote libraries for it, NamedType and type_safe. If you need something heavier-duty, you should check out Boost.Units. Note that I haven't used any of these myself; they're just where I'd check if I needed those features.

You didn't ask about this, but none of this should have any runtime performance costs over just using float everywhere and keeping track of units manually, but might make compilation slower.

strong typedef of std::string

You won't need to wrap every operation of std::string because you won't need to use them. All that the caller has to do is initialise the argument with a correct type with a value for the internal string:

struct address_type {
std::string value;
};

struct name_type {
std::string value;
};

struct info {
info(address_type address, name_type name)
: address_{std::move(address.value)}, name_{std::move(name.value)}
{}
info(address_type address)
: address_{std::move(address.value)}
{}
info(name_type name)
: name_{std::move(name.value)}
{}
private:
std::string address_;
std::string name_;
};


info with_address {address_type{"str"}};
info with_name {name_type {"str"}};

But if you want to proceed with your approach of providing string functionality with your custom type, then you must wrap all the operations. That's going to be a ton of boilerplate and there's no magic to avoid that.



Related Topics



Leave a reply



Submit