SirSydom
SirSydom

Reputation: 21

C++: Datatype of a "number constant"

I had in mind the numeric constant in C++ (and also in C) have a datatype assigned. When these constants are an operand the datatype can influence the result and the result's datatype.

I know the datatype depends on the constant itself like 5 or 5.0 5.0f and it depends on the architecture/compiler.

But I don't know the rules exactly and searched a lot and it seems I didn't get the right keywords.

I did something like that:

inline unsigned long __DWORD(byte b0, byte b1, byte b2, byte b3) { return (unsigned long)((b0 << 24) + (b1 << 16) + (b2 << 8) + (b3 << 0)); }

and while it worked perfectly on a SAMD21 (32bit) it didn't work on atmega (8bit).

inline unsigned long __DWORD(byte b0, byte b1, byte b2, byte b3) { return (unsigned long)(((unsigned long)b0 << 24) + ((unsigned long)b1 << 16) + ((unsigned long)b2 << 8) + ((unsigned long)b3 << 0)); } that worked. And I'm sure casting the right operand would also have worked.

how can I know whats the default datatyp of a numeric constant is? Is the behaviour defined?

Upvotes: 0

Views: 84

Answers (1)

Bitwize
Bitwize

Reputation: 11220

It sounds as though you're conflating the concept of "data type" with the size of the underlying type -- which are two different things.

The type of an expression is fixed and well-defined in C++.

The size of a type, on the other hand, is implementation-defined. It depends entirely on the compiler (most-often, on the target architecture).

The C++ language gives us few guarantees on the size of these types[cppreference], which makes it unreliable for portability to use these types while expecting a specific size.

In general, you're better off using the <cstdint> header types for fixed-with integrals.

For example, your __DWORD example could be written:

inline std::uint32_t __DWORD(std::uint8_t b0, std::uint8_t b1, std::uint8_t b2, std::uint8_t b3) 
{ 
    return static_cast<std::uint32_t>(
      (static_cast<std::uint32_t>(b0) << 24) + 
      (static_cast<std::uint32_t>(b1) << 16) + 
      (static_cast<std::uint32_t>(b2) << 8) + 
      (static_cast<std::uint32_t>(b3) << 0)
    );
}

The reason the cast to uint32_t is needed for b0,b1,b2, and b3 is because the resultant type of a shift operations is always the type on the left-hand side of the operation. So if sizeof(b0) is 1, then b0 << 24 will shift all data out of b0 because it exceeds 8 bits. Casting to uint32_t ensures that the result of the shift will fit into the resultant type.

So to be clear: The type of the expression does not depend on the compiler or architecture; just the size of the underlying type.


On an unrelated note, any identifier containing two double underscores (__), or starting with an underscore followed by an uppercase letter (_D) are reserved for the implementation, and should not be used. Your __DWORD function should probable be renamed something like make_uint32(...) instead

Upvotes: 2

Related Questions