Warnings when initialising map using std::uint8_t as key

Viewed 300

Problem

I am trying to create a map of std::uint8_t -> char, and initialise it with some values:

const std::map<std::uint8_t, char> ScenarioReader::alphabet = {
    { 0x12, 'b' },
    { 0x13, 'c' },
    { 0x15, 'a' },
    { 0x16, 'f' },
    ...
}

This generates a compiler warning because these integer literals (0x12, etc.) are recognised as unsigned ints, which are larger than a std::uint8_t:

1>d:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\utility(172): warning C4244: 'initializing': conversion from '_Ty' to '_Ty1', possible loss of data
1>        with
1>        [
1>            _Ty=unsigned int
1>        ]
1>        and
1>        [
1>            _Ty1=uint8_t
1>        ]
1>d:\my-project\src\myfile.cpp(75): note: see reference to function template instantiation 'std::pair<const _Kty,_Ty>::pair<unsigned int,char,0>(_Other1 &&,_Other2 &&) noexcept' being compiled
1>        with
1>        [
1>            _Kty=uint8_t,
1>            _Ty=char,
1>            _Other1=unsigned int,
1>            _Other2=char
1>        ]
1>d:\my-project\src\myfile.cpp(12): note: see reference to function template instantiation 'std::pair<const _Kty,_Ty>::pair<unsigned int,char,0>(_Other1 &&,_Other2 &&) noexcept' being compiled
1>        with
1>        [
1>            _Kty=uint8_t,
1>            _Ty=char,
1>            _Other1=unsigned int,
1>            _Other2=char
1>        ]

Solutions

I am aware of 2 possible ways to fix this:

1) Disable warnings for this section

#pragma warning( push )
#pragma warning( disable : 4244 )
const std::map<std::uint8_t, char> ScenarioReader::alphabet = {
    { 0x12, 'b' },
    { 0x13, 'c' },
    { 0x15, 'a' },
    { 0x16, 'f' },
    ...
}
#pragma warning( pop)

2) Cast every key explicitly

const std::map<std::uint8_t, char> ScenarioReader::alphabet = {
    { static_cast<std::uint8_t>(0x12), 'b' },
    { static_cast<std::uint8_t>(0x13), 'c' },
    { static_cast<std::uint8_t>(0x15), 'a' },
    { static_cast<std::uint8_t>(0x16), 'f' },
    ...
}

I am not particularly happy about either approach, but the second is especially ugly to me.

Am I, perhaps, missing a simpler solution?

3 Answers

An integer literal can never be an std::uint8_t. Instead of using a static_cast, you can make an explicit cast of the literal:

const std::map<std::uint8_t, char> alphabet = {
    { std::uint8_t(0x12), 'b' },
    { std::uint8_t(0x13), 'c' },
    { std::uint8_t(0x15), 'a' },
    { std::uint8_t(0x16), 'f' },
};

You can write a user-define literal

#include <cstdint>

constexpr std::uint8_t operator "" _ui8(unsigned long long value)
{
    return static_cast<std::uint8_t>(value);
}

#include <type_traits>

int main() {
    static_assert(std::is_same_v<decltype(0x12_ui8), std::uint8_t>);
}

The warning is an erroneous one and it happens only on \W4 warning level.

A conversion from a literal to std::uint8_t should not generate any warnings, unless the literal is out of range of the converted to type, which is not the case here.

Any option you choose is OK in my opinion. It just boils down to a matter of personal preference. I personally would prefer the std::uint8_t{0x12} syntax.

Another option would be to go down one warning level to \W3 and then use a separate linter (\W4 is mostly used for lint-like warnings anyway).

Related