sipickles
sipickles

Reputation: 1662

Declare types without implicit conversion in C++

I want to declare my own numeric types, exactly like unsigned int, but I do not want the types to be implicitly converted. I tried this first: typedef unsigned int firstID; typedef unsigned int secondID;

but this is no good as the two types are just synonyms for unsigned int, so are freely interchangable.

I'd like this to cause an error:

firstID fid = 0;
secondID sid = fid; // no implicit conversion error

but this to be okay:

firstID fid = 0;
secondID sid = static_cast<secondID>(fid); // no error

My reason is so that function arguments are strongly typed, eg:

void f( firstID, secondID ); // instead of void f(unsigned int, unsigned int)

What is the mechanism I am looking for?

Thanks

Si

Upvotes: 21

Views: 2425

Answers (6)

JWCS
JWCS

Reputation: 1201

The venerable DBJ has a very interesting solution, as always. Here is his article Cpp How to Avoid Implicit Conversions and the simple header adding basic operators for the type. Like others have mentioned, it's a templated class, but unlike the other answers I found here, here is a solution that's tested and works (err, outside of importing all of boost).

Then in practice you can declare a type without implicit conversions almost like normal, wrapping in his header.

using my_safe_uint16_t = dbj::util::nothing_but<uint16_t>;

Upvotes: 1

Loki Astari
Loki Astari

Reputation: 264391

As you noted: typedef is badly named (it should be typealias (D has explicitly added typealias (last time I looked))

So the only way you can do this is to create two unique classes.
I am not going to say you can't write a specialization of static_cast<> to do what you want, but I think (and I have not put that much thought into it yet) doing so would be a bad idea (even if it legal), I think a better approach is to have each class constructor use unsigned int that are explicit (so there is no auto conversion).

 struct FID
 {
     explicit FID(unsigned int v): value(v) {}
     operator unsigned int() {return value;}
     unsigned int value;
 };

 class SID {/* Stuff */};

 FID   fid(1U);
 SID   sid(2U);

 doSomthingWithTwoSID(sid,SID(static_cast<unsigned int>(fid));

Making the constructor explicit means no auto conversion between types.
By adding the built in cast operator to unsigned int means that it can be used anywhere an int is expected.

Upvotes: 4

T.E.D.
T.E.D.

Reputation: 44804

Ahhh, a fellow Ada traveler I see.

The only real way to do this in C++ is to declare classes. Something like:

class first_100 {
public:
    explicit first_100(int source) {
        if (source < 1 || source > 100) {
           throw range_error;
        }
        value = source;
    };
    // (redefine *all* the int operators here)
private:
    int value;
};

You'll want to make sure to define your int constructor explicit so that C++ won't use it to implicitly convert between your types. That way this will not work:

first_100 centum = first_100(55);
int i = centum;

but something like this might (assuming you define it):

int i = centum.to_int();

Upvotes: 1

agsamek
agsamek

Reputation: 9054

struct FirstID { int id; };

Upvotes: 1

Alexander Poluektov
Alexander Poluektov

Reputation: 8053

Maybe BOOST_STRONG_TYPEDEF form boost/strong_typedef.hpp would help.

Upvotes: 8

Matteo Italia
Matteo Italia

Reputation: 126787

You have to write your own classes for them, reimplementing all the operators you need.

Upvotes: 2

Related Questions