Strongly-typed typedefs in C++ in MinGW and GCC (C++11, GCC v4.6)

Published May 29, 2012
Advertisement
C++ typedefs are weak - they are only a simple alias of the original type, and are implicitly converted to and from the original type.

Here's a basic example:
struct Point
{
int x, y;
};

typedef Point MyPoint;
typedef Point MyOtherPoint;

int main(int argc, char *argv[])
{
Point point;
MyPoint myPoint;
MyOtherPoint myOtherPoint;

point = myPoint; //Implict conversion from typedef and original.
myPoint = point; //Implicit conversion from original to typedef.
myOtherPoint = myPoint; //Implicit conversion between two different typedefs.

return 0;
}


Unfortunately, C++11 doesn't add strong typedefs either, but using some of the new features in C++11 I created a template/macro that adds strong typedefs to the language.

Here's how it's used:
//typedef Point MyPoint;
//typedef Point MyOtherPoint;

strong_typedef( Point, MyPoint);
strong_typedef( Point, MyOtherPoint);

int main(int argc, char *argv[])
{
Point point;
MyPoint myPoint;
MyOtherPoint myOtherPoint;

point = myPoint; //Implict conversion from typedef and original.
myPoint = point; //error: static assertion failed: "MyPoint can not implicitly be assigned from Point-derived classes or strong Point typedefs"
myOtherPoint = myPoint; //error: static assertion failed: "MyPoint can not implicitly be assigned from Point-derived classes or strong Point typedefs"

return 0;
}


The 'strong_typedef' template/macro allows you to use the same constructors as the original, and even inherits most operators, but blocks implicit conversion when accidentally mixing types in an operation (myPoint += myOtherPoint; //compile error! Mixing types).

The macro also adds two new functions the the new types, to allow explicit conversion: "ToBaseClass()" and "FromBaseClass()".

There may be a few problems with this macro still, and if so, I'd love to hear it. Especially since template-metaprogramming type stuff is new to me, I may be making mistakes somewhere - if more experienced users would peer-review and enhance the code, we all benefit.

Here's the complete class, with example usage: (Requires GCC 4.6 or greater, and you must enable C++11 features)
#include
#include
#include

#include

//Uncomment this line if you want two seperate strong typedefs to allow non-strong comparison ("strongTypeA > strongTypeB", and etc...).
//This only applies to comparisons - assignment, manipulation, and construction is still strong.
//#define STRONG_TYPEDEF_ALLOW_WEAK_COMPARISONS

//---------------------------------------------------------------------------------
//These macroes are used by 'strong_typedef'.

#define strtype_enable_if_has_function(parentTemplateArgument, function, returnValue) \
typename std::enable_if< std::is_member_function_pointer::value , returnValue>::type

#define strtype_assert_can_call(parentType, newTypeName, templateArg, errorMessage) \
static_assert((std::is_base_of::type >::type >::value == false) \
|| std::is_same::type >::type >::value, errorMessage)

#define strtype_inherit_standalone_arithmatic_operator(parentType, toFromFunctionParentName, newTypeName, function) \
template \
strtype_enable_if_has_function(Parent, function, newTypeName) \
function(const Arg &other) const \
{ \
strtype_assert_can_call(parentType, newTypeName, Arg, \
"" #newTypeName " is not implicitly convertible with " #parentType "-derived classes or strong " #parentType " typedefs"); \
return From ## toFromFunctionParentName(this->Parent::function(other)); \
} \
/*newTypeName function(const parentType &other) = delete */ /* Delete the direct conversion */

#define strtype_inherit_compound_arithmatic_operator(parentType, toFromFunctionParentName, newTypeName, function) \
template \
strtype_enable_if_has_function(Parent, function, const newTypeName &) \
function(const Arg &other) \
{ \
strtype_assert_can_call(parentType, newTypeName, Arg, \
"" #newTypeName " is not implicitly convertible with " #parentType "-derived classes or strong " #parentType " typedefs"); \
this->Parent::function(other); \
return *this; \
} \
/*const newTypeName &function(const parentType &other) = delete*/ /* Delete the direct conversion */

#ifdef STRONG_TYPEDEF_ALLOW_WEAK_COMPARISONS
#define strtype_inherit_comparison_operator(parentType, toFromFunctionParentName, newTypeName, function) \
template \
strtype_enable_if_has_function(Parent, function, bool) \
function(const Arg &other) const \
{ \
return this->Parent::function(other); \
} \
/*const newTypeName &function(const parentType &other) = delete*/ /* Delete the direct conversion */
#else
#define strtype_inherit_comparison_operator(parentType, toFromFunctionParentName, newTypeName, function) \
template \
strtype_enable_if_has_function(Parent, function, bool) \
function(const Arg &other) const \
{ \
strtype_assert_can_call(parentType, newTypeName, Arg, \
"" #newTypeName " is not implicitly comparable to " #parentType "-derived classes or strong " #parentType " typedefs." \
" (To implicitly allow weak type comparison, define STRONG_TYPEDEF_ALLOW_WEAK_COMPARISONS)"); \
\
return this->Parent::function(other); \
} \
/*const newTypeName &function(const parentType &other) = delete*/ /* Delete the direct conversion */
#endif

#define strtype_inherit_suffix_operator(parentType, toFromFunctionParentName, newTypeName, function) \
template \
strtype_enable_if_has_function(Parent, function, newTypeName) \
function(int dummy) const \
{ \
return From ## toFromFunctionParentName(this->Parent::function(dummy)); \
} \
/*const newTypeName &function(const parentType &other) = delete*/ /* Delete the direct conversion */

#define strtype_inherit_prefix_operator(parentType, toFromFunctionParentName, newTypeName, function) \
template \
strtype_enable_if_has_function(Parent, function, newTypeName &) \
function() const \
{ \
this->Parent::function(); \
return *this; \
} \
/*const newTypeName &function(const parentType &other) = delete*/ /* Delete the direct conversion */

/*
Example usage:

strtype_inherit_standalone_arithmatic_operator(parentType, toFromFunctionParentName, newTypeName, operator+,
"" #newTypeName " cannot be assigned from " #parentType "-derived classes or strong " #parentType " typedefs")
*/
//---------------------------------------------------------------------------------

//Defines a new type that behaves exactly as 'parentType', with the added benefit that
//you can't implicitly convert between types, which helps reduce errors.
#define strong_typedef(parentType, newTypeName) \
class newTypeName : public parentType \
{ \
public: \
/* No-arg constructor */ \
newTypeName() {} \
\
/* Copy-constructor */ \
newTypeName(const newTypeName &other) \
{ \
this->operator=(other); \
} \
\
/* Single-argument constructor passes arguments to parent's constructor, but */ \
/* generates a compile-time error if you try to use the parent's copy-constructor. */ \
template \
newTypeName(Arg &&arg) : parentType(std::forward(arg)) \
{ \
static_assert((std::is_base_of::type >::type >::value == false) \
|| std::is_same::type >::type >::value, \
"" #newTypeName " cannot be constructed from " #parentType "-derived classes or strong " #parentType " typedefs"); \
} \
\
/* Multi-argument constructor, passes parameters to parent's constructor. */ \
template \
newTypeName(Args &&... args) : parentType(std::forward(args)...) { } \
\
/* Explicitly convert me from parent */ \
static newTypeName From ## parentType(const parentType &other) \
{ \
newTypeName newType; \
newType.parentType::operator=(other); \
return newType; \
} \
\
/* Explicitly convert me to parent */ \
const parentType &To ## parentType() const \
{ \
return *this; \
} \
\
/* Assignment operator */ \
template \
strtype_enable_if_has_function(Parent, operator=, newTypeName &) \
operator=(const Arg &other) const \
{ \
strtype_assert_can_call(parentType, newTypeName, Arg, \
"" #newTypeName " can not implicitly be assigned from " #parentType "-derived classes or strong " #parentType " typedefs"); \
\
return this->Parent::operator=(other); \
} \
\
/* Negation operator (Logical NOT) */ \
template \
strtype_enable_if_has_function(Parent, operator!, bool) \
operator!() const \
{ \
return this->Parent::operator!(); \
} \
\
/* Other operators */ \
strtype_inherit_standalone_arithmatic_operator(parentType, toFromFunctionParentName, newTypeName, operator+); \
strtype_inherit_standalone_arithmatic_operator(parentType, toFromFunctionParentName, newTypeName, operator-); \
strtype_inherit_standalone_arithmatic_operator(parentType, toFromFunctionParentName, newTypeName, operator/); \
strtype_inherit_standalone_arithmatic_operator(parentType, toFromFunctionParentName, newTypeName, operator*); \
strtype_inherit_standalone_arithmatic_operator(parentType, toFromFunctionParentName, newTypeName, operator%); \
\
strtype_inherit_standalone_arithmatic_operator(parentType, toFromFunctionParentName, newTypeName, operator<<); \
strtype_inherit_standalone_arithmatic_operator(parentType, toFromFunctionParentName, newTypeName, operator>>); \
\
strtype_inherit_compound_arithmatic_operator(parentType, toFromFunctionParentName, newTypeName, operator+=); \
strtype_inherit_compound_arithmatic_operator(parentType, toFromFunctionParentName, newTypeName, operator-=); \
strtype_inherit_compound_arithmatic_operator(parentType, toFromFunctionParentName, newTypeName, operator/=); \
strtype_inherit_compound_arithmatic_operator(parentType, toFromFunctionParentName, newTypeName, operator*=); \
strtype_inherit_compound_arithmatic_operator(parentType, toFromFunctionParentName, newTypeName, operator%=); \
\
strtype_inherit_comparison_operator(parentType, toFromFunctionParentName, newTypeName, operator==); \
strtype_inherit_comparison_operator(parentType, toFromFunctionParentName, newTypeName, operator!=); \
strtype_inherit_comparison_operator(parentType, toFromFunctionParentName, newTypeName, operator>); \
strtype_inherit_comparison_operator(parentType, toFromFunctionParentName, newTypeName, operator<); \
strtype_inherit_comparison_operator(parentType, toFromFunctionParentName, newTypeName, operator>=); \
strtype_inherit_comparison_operator(parentType, toFromFunctionParentName, newTypeName, operator<=); \
\
strtype_inherit_suffix_operator(parentType, toFromFunctionParentName, newTypeName, operator++); \
strtype_inherit_suffix_operator(parentType, toFromFunctionParentName, newTypeName, operator--); \
\
strtype_inherit_prefix_operator(parentType, toFromFunctionParentName, newTypeName, operator++); \
strtype_inherit_prefix_operator(parentType, toFromFunctionParentName, newTypeName, operator--); \
}

struct Size
{
Size(int width = 0, int height = 0) : width(width), height(height) { }
Size(const Size &size) { *this = size; }
~Size() { }

int width;
int height;

bool operator==(const Size &other) const
{
return (this->width == other.width && this->height == other.height);
}

bool operator!=(const Size &other) const
{
return !(*this == other);
}

Size &operator=(const Size &other) //Assignment operator
{
if(this == &other) return *this; //Self-assignment protection.

this->width = other.width;
this->height = other.height;

return *this;
}

Size operator+(const Size &other) const
{
return Size(this->width + other.width,
this->height + other.height);
}

Size &operator+=(const Size &other)
{
this->width += other.width;
this->height += other.height;

return *this;
}

Size operator-(const Size &other) const
{
return Size(this->width - other.width,
this->height - other.height);
}

Size &operator-=(const Size &other)
{
this->width -= other.width;
this->height -= other.height;

return *this;
}
};

strong_typedef(Size, MySize, Size);
strong_typedef(Size, MyOtherSize, Size);

int main(int argc, char *argv[])
{
Size originalSize;

MySize mySizeA;
MySize mySizeB;

MyOtherSize myOtherSize;

mySizeA += mySizeB;
mySizeA = (mySizeA + mySizeB);

mySizeA -= mySizeB;
mySizeA = (mySizeA - mySizeB);

//Only compiles if the original type has those operators implemented:
{
//mySizeA /= mySizeB;
//mySizeA = (mySizeA / mySizeB);

//mySizeA *= mySizeB;
//mySizeA = (mySizeA * mySizeB);
}

bool isEqualTo = (mySizeA == mySizeB);
bool isNotEqualTo = (mySizeA != mySizeB);

//Only compiles if the original type has those operators implemented:
{
//(mySizeA >= mySizeB);
//(mySizeA <= mySizeB);
//(mySizeA > mySizeB);
//(mySizeA < mySizeB);
}

//Fails to compile when accidentally mixing types:
//static assertion failed: "MySize cannot be constructed from Size-derived classes or strong Size typedefs"
{
//mySizeA = myOtherSize;

//mySizeA += myOtherSize;
//mySizeA -= myOtherSize;
//mySizeA *= myOtherSize;
//mySizeA /= myOtherSize;

//mySizeA = (mySizeA + myOtherSize);
//mySizeA = (mySizeA - myOtherSize);
//mySizeA = (mySizeA * myOtherSize);
//mySizeA = (mySizeA / myOtherSize);

//mySizeA = originalSize;
}

//Only compiles if 'STRONG_TYPEDEF_ALLOW_WEAK_COMPARISONS' is defined.
//static assertion failed: "MySize is not implicitly comparable to Size-derived classes or strong Size typedefs"
{
//bool isEqualTo = (mySizeA == myOtherSize);
//bool isNotEqualTo = (mySizeA != myOtherSize);
}

return 0;
}


[size=2]Note: This macro only works with structs and classes, not with basic types like floats and ints (I haven't actually tried it, but I'd guess it wouldn't compile - you can use BOOST_STRONG_TYPEDEF for basic types instead. Boost's strong typedef doesn't work for classes and structs, which is why I made my version).

I don't suggest using strong_typedefs to randomly replace regular typedefs everywhere you use them, but I do think it's wise to use strong_typedefs for types that are likely to cause hard-to-find bugs. Unit conversions, in my case; I have Point classes that sometime are measured in pixels, and other times are measured in tiles. Sometimes they are in pixels from the origin of the world, and sometimes only from the origin of the currently loaded maps. Accidental implicit conversions are likely to occur in such situations, but making the different unit measures their own strongly typed typedefs provides compile-time error messages when a mistake occurs, instead of run-time logical bugs that are harder to locate.

Review and enhancement of the code by more advanced programmers is definitely welcome and requested! In particular, am I using perfect forwarding correctly in the constructors and operators? And also, I'm having difficulties getting the array subscript 'operator[]' and function-call 'operator()' operators to work properly in a generic way ([size=2]since the return value types of those operators aren't know, using it with std::enable_if is causing me difficulties).
4 likes 6 comments

Comments

TheUnbeliever
If it's of any help, I handled operator() in my pipeline library using Boost:

[CODE]typedef BOOST_TYPEOF(&T::operator()) MemberOperatorPtr;
typedef typename boost::function_types::result_type<MemberOperatorPtr>::type Result;[/CODE]

(n.b. If I recall, the first typedef is necessary - you can't just substitute into the second.)
May 29, 2012 07:55 PM
Servant of the Lord
Thanks, I'll try that out

The problem is I don't know ahead of time if the parent class has operator() or not, so I can't call decltype(T::operator()) or a compiler error results just from that.

To enable_if() the function, I have to know ahead of time the return type... but unlike the other operators, the return type for operator() and operator[] is user-defined.

[CODE]std::enable_if(...has operator[]..., ...return value...)::type operator[]
{ ... }[/CODE]


The problem is, the second parameter of 'std::enable_if' has to exist before '[i]...has operator[]...[/i]' is checked. So if I do:
[code]std::enable_if(...has operator[]..., ...return value of operator[]...)::type operator[][/code]
...it fails to compile, because '[i]return value of operator[][/i]' results in, "Parent doesn't have operator[]!" before '[i]...has operator[]...[/i]' has an opportunity to evaluate false.

This is the ideal location to go:
[CODE]auto operator[] -> decltype(operator[])[/CODE]

But std::enable_if() doesn't like 'auto' as a parameter. This fails to compile:
[code]std::enable_if(...has operator[]..., auto)::type operator[] -> decltype(operator[])[/code]
May 29, 2012 08:35 PM
Sarfaraz Nawaz
[color=#333333]It looks very cumbersome. Why not use templates as:[/color]

[color=#333333]typedef basic_point<tag_point> point;[/color]
[color=#333333]typedef basic_point<tag_other_poin[/color][color=#333333]t> other_point;[/color]
[color=#333333]typedef basic_point<tag_some_point[/color][color=#333333]> some_point;[/color]

[color=#333333]Now each of them is a different type, and therefore cannot be implicitly converted into others (unless you provide required constructor and assignment to allow this explicitly).[/color]
May 30, 2012 03:01 PM
Servant of the Lord
I'm not sure I understand. Where are you getting 'tag_point', 'tag_other_point' from? Are they just empty dummy structs to feed to the template?

Also, with this method, wouldn't I need to create a new template type (like 'basic_point') for every class I want to make strong typedefs of? [i]That [/i]seems cumbersome. I have to make my Point class in a special way to get it to work, I'd have to make my Size class a special way to get it to work, I'd have to remake any and every class that wants strong typedefs.

With my way, if I want a strong typedef of a string (for whatever reason), I'd just go like this:
[CODE]strong_typedef( std::string, MyString );[/CODE]
(... or I would, if it worked, but apparently it doesn't. Oops, should've tested with std::string earlier)

However, you're right that turning the entire macro into a template [u]would[/u] make alot of sense. I'll see if I can do that.
It'd look more like this:
[code]typedef strong_type<Point> MyPoint;[/code]
...with only a single template type needed (strong_type) instead of a different for every class.

However, if I did do that, it'd bring me right back to where I started! There would be implicit conversion. if the templates use the same parameters.
[code]typedef strong_type<Point> MyPoint;
typedef strong_type<Point> MyOtherPoint;

MyPoint myPoint;
MyOtherPoint myOtherPoint = myPoint; //Implicit conversion again![/code]

I could still make it templated... but provide a dummy [i]second [/i]parameter.
[code]typedef strong_type<Point, UniqueDummyType> MyPoint;
typedef strong_type<Point, DifferentUniqueDummyType> MyOtherPoint;[/code]

...which, seeing that I'd have to declare the unique dummy types, I'd just wrap the whole thing in a macro...
[code]strong_typedef(parentType, newType) struct newType ## _dummyType { int unused; }; \
typedef strong_type<parentType, newType ## _dummyType> newType;[/code]

Which would require me to use it like this: =)
[code]strong_typedef(Point, MyPoint);[/code]

But with the greatly added benefit that the main logic of the thing is not wrapped in a define, easing developer maintenance of the strong_typedef code...
...at the added expense of slightly increasing compile time per strong typedef (maybe).

Still, it might be worth making the switch.
May 30, 2012 04:29 PM
popsoftheyear
Oh cool - I was going to comment on your prev. blog entry and ask how you went about this. Thanks for sharing!

One minor things stood out to me. I noticed the copy constructor uses the assignment operator internally. Isn't this at least slightly inefficient? *googling* [url="http://msdn.microsoft.com/en-us/magazine/cc163742.aspx"]http://msdn.microsoft.com/en-us/magazine/cc163742.aspx[/url] mentions this (usually minor) issue.
May 30, 2012 05:47 PM
Servant of the Lord
I never knew that, thanks!
I didn't even realize that the copy-constructor was a legitimate 'constructor' that can use initialization lists. =P
(I thought it was just a special operator that just happened to also be called 'constructor' because of the similar interface) Good to know!
May 30, 2012 07:11 PM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement