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).
[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.)