Louis Caron
Louis Caron

Reputation: 1332

Is there a GCC warning that can indicate any type change?

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

Answers (3)

Jorenar
Jorenar

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

Phantomas
Phantomas

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

Kaz
Kaz

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

Related Questions