Reputation: 1332
I have the following typedefs in my code:
#define FOO_OFF 0
#define FOO_ON 1
typedef uint8_t foo;
#define BAR_NO 0
#define BAR_YES 1
#define BAR_UNKNOWN 255
typedef uint8_t bar;
Those two types, although they have the same underlying type, they do not carry the same information.
And actually, I would like to get a warning if anyone in the code does something like:
foo foovar = FOO_OFF;
void get_bar(bar *outvar)
{
// assigning the bar variable a foo variable content
*outvar = foovar;
}
I could not find any such warning option in gcc, I have come accross -Wconversion
but this warns only if there is a chance of losing information which is not the case in my example.
Does anyone know if there is something I can do? Obviously, it should be possible to cast when a type change is really needed.
Upvotes: 4
Views: 581
Reputation: 2884
I quite like the solution the workmates of author of question Strongly typed using and typedef have come up with, as it's really minimal and easy:
#define STRONG_TYPEDEF(T, D) \
class D : public T { \
public: \
template<class... A> explicit D(A... a) : T(a...) {} \
const T& raw() const { return *this; } \
}
Unfortunately it won't work with primitive types. But it's similar to BOOST_STRONG_TYPEDEF
, which will do.
Here is a little example and vestigial "comparison" of both:
#include <iostream>
#define USE_BOOST true
#if USE_BOOST
#include <boost/serialization/strong_typedef.hpp>
BOOST_STRONG_TYPEDEF(std::string, foo);
BOOST_STRONG_TYPEDEF(std::string, bar);
BOOST_STRONG_TYPEDEF(int, myInt); // Boost allows primitives
#else
#define STRONG_TYPEDEF(T, D) \
class D : public T { \
public: \
template<class... A> explicit D(A... a) : T(a...) {} \
const T& raw() const { return *this; } \
}
STRONG_TYPEDEF(std::string, foo);
STRONG_TYPEDEF(std::string, bar);
// STRONG_TYPEDEF(int, myInt); // this one just classes
#endif
int main()
{
std::string a = "abc";
foo b{"abc"};
bar c = static_cast<bar>(a);
std::string d = c; // we can assign back to base type
#if USE_BOOST // Boost
myInt x{4}; // only allows this type of initialization
switch (x) { // allows primitives, so `switch` works
case 1:
std::cout << 1 << std::endl;
break;
case 4:
std::cout << 4 << std::endl;
break;
}
#else // Boost don't allow the following:
if (b == c) { // comparing
std::cout << c << std::endl; // printing
}
#endif
/* But we can't
foo e = a; // assign base type to new type
foo f = c; // assign one type to another
*/
return 0;
}
Upvotes: 0
Reputation: 206
Short answer is no, you can't do this. typedef
declare an alias, not a new type, so any compiler respecting the standard cannot have the feature you want right now.
However, you can achieve it, by introducing a new type, using an enum, or a struct.
If you're in C, you will be able to cast from one enum to the other easily.
Because the address of the first element of a struct is also the address of a struct, you would be able to cast it from and to int8 or another struct, by casting the struct address, then de-referencing the pointer with it's new type. (*((dest_type *)&value)
)
Upvotes: 3
Reputation: 58598
If the size doesn't have to be one byte, we can (ab)use pointers:
typedef struct foo *rolloverdetection;
typedef struct bar *rolloverdetected;
#define ROLLOVERDETECTION_OFF ((rolloverdetection) 0)
#define ROLLOVERDETECTION_ON ((rolloverdetection) 1)
#define ROLLOVERDETECTED_NO ((rolloverdetected) 0)
#define ROLLOVERDETECTED_YES ((rolloverdetected) 1)
#define ROLLOVERDETECTED_UNKNOWN ((rolloverdetected) 2)
The preprocessor symbols aren't constant expressions any more and we can't use them as switch labels and whatnot.
A good solution to this is to use C++ type-safe enums
. This is one of the advantages of writing your code in "Clean C": an informal name given to working in a language dialect which compiles as some version of C, as well as some version of C++ with the same behavior.
Simply:
typedef enum {
ROLLOVERDETECTION_OFF,
ROLLOVERDETECTION_ON
} rolloverdetection;
typedef enum {
ROLLOVERDETECTED_NO,
ROLLOVERDETECTED_YES,
ROLLOVERDETECTED_UNKNOWN = 255
} rolloverdetected;
In C, you can still assign ROLLOVERDETECTED_YES
to a variable of type rolloverdetection
, but not so in C++.
If you keep the code compiling as C++, you can use a C++ compiler to check for these violations, even if the shipping build of the code doesn't use C++.
If storing the value in 8 bits is important, I seem to recall GCC supports enum-typed bitfields as an extension (not in ISO C):
struct whatever {
rolloverdetected roll_detect : 8;
};
C++ enums are not perfectly type safe; implicit conversion from an enum
member to an integer type is possible:
int roll = ROLLOVERDETECTION_ON;
but not in the reverse direction.
By the way, other techniques open up if you compile as C and C++, like being able to use more nuanced type casts:
#ifdef __cplusplus
#define strip_qual(TYPE, EXPR) (const_cast<TYPE>(EXPR))
#define convert(TYPE, EXPR) (static_cast<TYPE>(EXPR))
#define coerce(TYPE, EXPR) (reinterpret_cast<TYPE>(EXPR))
#else
#define strip_qual(TYPE, EXPR) ((TYPE) (EXPR))
#define convert(TYPE, EXPR) ((TYPE) (EXPR))
#define coerce(TYPE, EXPR) ((TYPE) (EXPR))
#endif
So now, for instance, we can do
strip_qual(char *, str)
In C, this is just an unsafe (char *) str
cast. In C++, the above macros produce const_cast<char *>(str)
. So if str
starts out being const char *
, but then someone changes it to const wchar_t *
, the C++ compiler will diagnose the above cast. Yet, our project doesn't require a C++ compiler in order to build.
In conjunction with this, if you're using GCC, its C++ front end has -Wold-style-cast
that will find all the places in the code where you're using the (type) value
cast notation.
Upvotes: 1