-2

I'm writing my own little utility library, and I've opted to do something which I don't really see often (and there might be a reason) - I'm trying to implement a 'perfect' wrapper around C++ integer classes (think int, long long, etc.). I've managed to do this, and it works just fine, but I keep getting warnings from static cast invocations (C4244).

My implementation:

This is my implementation for a 'replacement' for the int type, other types are implemented in the same way, but, for the sake of brevity, I'll just share the int one. (in my source code, I've implemented this using macros).

struct i32 {
    constexpr i32() = default;
    constexpr i32(int32_t v) : value(v) {}
    constexpr i32(const i32& b) = default;
    constexpr i32(i32&& b) noexcept : value(std::exchange(b.value, 0)) {}

    ~i32() = default;

    constexpr auto operator=(const i32& b) -> i32& {
        if(this != &b) {
            value = b.value;
        }
        return *this;
    }
    constexpr auto operator=(i32&& b) noexcept -> i32& {
        if(this != &b) {
            value = std::exchange(b.value, 0);
        }
        return *this;
    }

    constexpr auto operator+=(int32_t x) -> i32& {
        value += x;
        return *this;
    }
    constexpr auto operator-=(int32_t x) -> i32& {
        value -= x;
        return *this;
    }
    constexpr auto operator*=(int32_t x) -> i32& {
        value *= x;
        return *this;
    }
    constexpr auto operator%=(int32_t x) -> i32& {
        value %= x;
        return *this;
    }
    constexpr auto operator&=(int32_t x) -> i32& {
        value &= x;
        return *this;
    }
    constexpr auto operator|=(int32_t x) -> i32& {
        value |= x;
        return *this;
    }
    constexpr auto operator^=(int32_t x) -> i32& {
        value ^= x;
        return *this;
    }
    constexpr auto operator<<=(int32_t x) -> i32& {
        value <<= x;
        return *this;
    }
    constexpr auto operator>>=(int32_t x) -> i32& {
        value >>= x;
        return *this;
    }
    constexpr auto operator/=(int32_t x) -> i32& {
        value /= x;
        return *this;
    }

    constexpr auto operator++() -> i32& {
        ++value;
        return *this;
    }
    constexpr auto operator++(int) -> i32 {
        i32 tmp(*this);
        ++(*this);
        return tmp;
    }
    constexpr auto operator--() -> i32& {
        --value;
        return *this;
    }
    constexpr auto operator--(int) -> i32 {
        i32 tmp(*this);
        --(*this);
        return tmp;
    }
    constexpr auto operator~() const -> i32 {
        return i32(~value);
    }

    constexpr operator int32_t() const {
        return value;
    }

    int32_t value;
};

Simple test case

u64 a = 10; // u64 is a uint64_t replacement
i32 b = static_cast<i32>(a); // argument: conversion from uint64_t to int32_t, possible loss of data.

std::cout << a << '\n'; // works as expected, outputs '10'

How can I fix this and prevent the warning from appearing (note: I'm not looking for a band aid solution, but a real fix).

I'm using MSVC with C++20

Why?

I've opted to reimplement a section of the standard library, both for the purposes of learning, and to get rid of the compilation overhead. I've thought it would be nice to be able to, instead of using std::numeric_limits<x>::max() / whatever, do something like i32::max(), and it kind of devolved from there.

9
  • 1
    "I've implemented this using macros" - Why not by using a template?
    – Ted Lyngmo
    Commented Jul 7 at 17:18
  • @TedLyngmo no real reason, they're just the first thing that I thought of. I suppose templates would work as well, maybe better. I'll try them out. Commented Jul 7 at 17:20
  • 1
    The warning is there for a purpose. You can't convert A uint64_t to a int321_t without losing data unless the value of the uint64_t is in the range of the int32_t. Commented Jul 7 at 17:22
  • 1
    What is u64? I don't understand what a uint64_t replacement is. Is it yet another class like i32?
    – 3CxEZiVlQ
    Commented Jul 7 at 17:24
  • If you cast directly from uint64_t to int32_t, you'll get this warning, too. So your replacement works exactly like the real deal. Is this not what you want? Commented Jul 7 at 17:27

2 Answers 2

1

Cast of class is actually a call to constructor/conversion operator.

and so

i32 b = static_cast<i32>(a);

is

i32 b = i32(a.operator std::int64_t());

Which calls i32(std::int32_t) and you got warning from the conversion from std::int64_t to std::int64_t.

Adding (explicit) constructor with those parameter would avoid the warning:

struct i32 {
    constexpr i32() = default;
    constexpr i32(int32_t v) : value(v) {}

    constexpr explicit i32(int64_t v) : value(static_cast<int32_t>(v)) {}

   // ...
};

Demo

0

Using an explicit templated constructor can do the trick, however, the entire idea is just dumb, as there are instances where only the base integer types can be used, making this useless (ie. enums).

An example of a solution can look like this:

struct integral {}

template<typename type>
concept is_integral = std::derived_from<type, integral>;

struct u64 : integral { ... }

struct i32 : integral {
  ...
  template<is_integral type>
  constexpr explicit i32(type t) : value(static_cast<int32_t>(t.value) {}

  int32_t value;
}

Note that this solution is basically what @Jarod42 suggested, just with templates.

3
  • "dumb" and "useless" is pretty strong. The idea could be used to implement strong typing, which is generally a good thing.
    – Ted Lyngmo
    Commented Jul 7 at 18:19
  • Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
    – Community Bot
    Commented Jul 8 at 0:15
  • I've updated the answer to better reflect the proposed solution. Commented Jul 8 at 1:49

Not the answer you're looking for? Browse other questions tagged or ask your own question.