aboutsummaryrefslogtreecommitdiffstats
path: root/util/generic/cast.h
blob: 882f2ca08fbeb0c20ae6d30db28c7ddbce46a2e4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#pragma once

#include "typetraits.h"
#include "yexception.h"

#include <util/system/compat.h>
#include <util/system/type_name.h>
#include <util/system/unaligned_mem.h>
#include <util/system/yassert.h>

#include <cstdlib>

template <class T, class F>
static inline T VerifyDynamicCast(F f) {
    if (!f) {
        return nullptr;
    }

    T ret = dynamic_cast<T>(f);

    Y_ABORT_UNLESS(ret, "verify cast failed");

    return ret;
}

#if !defined(NDEBUG)
    #define USE_DEBUG_CHECKED_CAST
#endif

namespace NPrivate {
    template <typename T, typename F>
    static T DynamicCast(F f) {
        return dynamic_cast<T>(f);
    }
} // namespace NPrivate

/*
 * replacement for dynamic_cast(dynamic_cast in debug mode, else static_cast)
 */
template <class T, class F>
static inline T CheckedCast(F f) {
#if defined(USE_DEBUG_CHECKED_CAST)
    return VerifyDynamicCast<T>(f);
#else
    /* Make sure F is polymorphic.
     * Without this cast, CheckedCast with non-polymorphic F
     * incorrectly compiled without error in release mode.
     */
    {
        auto&& x = &::NPrivate::DynamicCast<T, F>;

        (void)x;
    }

    return static_cast<T>(f);
#endif // USE_DEBUG_CHECKED_CAST
}

/*
 * be polite
 */
#undef USE_DEBUG_CHECKED_CAST

template <bool isUnsigned>
class TInteger;

template <>
class TInteger<true> {
public:
    template <class TUnsigned>
    static constexpr bool IsNegative(TUnsigned) noexcept {
        return false;
    }
};

template <>
class TInteger<false> {
public:
    template <class TSigned>
    static constexpr bool IsNegative(const TSigned value) noexcept {
        return value < 0;
    }
};

template <class TType>
constexpr bool IsNegative(const TType value) noexcept {
    return TInteger<std::is_unsigned<TType>::value>::IsNegative(value);
}

namespace NPrivate {
    template <class T>
    using TUnderlyingTypeOrSelf = typename std::conditional<
        std::is_enum<T>::value,
        std::underlying_type<T>, // Lazy evaluatuion: do not call ::type here, because underlying_type<T> is undefined if T is not an enum.
        std::enable_if<true, T>  // Wrapping T in a class, that has member ::type typedef.
        >::type::type;           // Left ::type is for std::conditional, right ::type is for underlying_type/enable_if

    template <class TSmall, class TLarge>
    struct TSafelyConvertible {
        using TSmallInt = TUnderlyingTypeOrSelf<TSmall>;
        using TLargeInt = TUnderlyingTypeOrSelf<TLarge>;

        static constexpr bool Result = std::is_integral<TSmallInt>::value && std::is_integral<TLargeInt>::value &&
                                       ((std::is_signed<TSmallInt>::value == std::is_signed<TLargeInt>::value && sizeof(TSmallInt) >= sizeof(TLargeInt)) ||
                                        (std::is_signed<TSmallInt>::value && sizeof(TSmallInt) > sizeof(TLargeInt)));
    };
} // namespace NPrivate

template <class TSmallInt, class TLargeInt>
constexpr std::enable_if_t<::NPrivate::TSafelyConvertible<TSmallInt, TLargeInt>::Result, TSmallInt> SafeIntegerCast(TLargeInt largeInt) noexcept {
    return static_cast<TSmallInt>(largeInt);
}

template <class TSmall, class TLarge>
inline std::enable_if_t<!::NPrivate::TSafelyConvertible<TSmall, TLarge>::Result, TSmall> SafeIntegerCast(TLarge largeInt) {
    using TSmallInt = ::NPrivate::TUnderlyingTypeOrSelf<TSmall>;
    using TLargeInt = ::NPrivate::TUnderlyingTypeOrSelf<TLarge>;

    if (std::is_unsigned<TSmallInt>::value && std::is_signed<TLargeInt>::value) {
        if (IsNegative(largeInt)) {
            ythrow TBadCastException() << "Conversion '" << TypeName<TLarge>() << '{' << TLargeInt(largeInt) << "}' to '"
                                       << TypeName<TSmallInt>()
                                       << "', negative value converted to unsigned";
        }
    }

    TSmallInt smallInt = TSmallInt(largeInt);

    if (std::is_signed<TSmallInt>::value && std::is_unsigned<TLargeInt>::value) {
        if (IsNegative(smallInt)) {
            ythrow TBadCastException() << "Conversion '" << TypeName<TLarge>() << '{' << TLargeInt(largeInt) << "}' to '"
                                       << TypeName<TSmallInt>()
                                       << "', positive value converted to negative";
        }
    }

    if (TLargeInt(smallInt) != largeInt) {
        ythrow TBadCastException() << "Conversion '" << TypeName<TLarge>() << '{' << TLargeInt(largeInt) << "}' to '"
                                   << TypeName<TSmallInt>() << "', loss of data";
    }

    return static_cast<TSmall>(smallInt);
}

template <class TSmallInt, class TLargeInt>
inline TSmallInt IntegerCast(TLargeInt largeInt) noexcept {
    try {
        return SafeIntegerCast<TSmallInt>(largeInt);
    } catch (const yexception& exc) {
        Y_ABORT("IntegerCast: %s", exc.what());
    }
}

/* Convert given enum value to its underlying type. This is just a shortcut for
 * `static_cast<std::underlying_type_t<EEnum>>(enum_)`.
 */
template <typename T>
constexpr std::underlying_type_t<T> ToUnderlying(const T enum_) noexcept {
    return static_cast<std::underlying_type_t<T>>(enum_);
}

// std::bit_cast from c++20
template <class TTarget, class TSource>
TTarget BitCast(const TSource& source) {
    static_assert(sizeof(TSource) == sizeof(TTarget), "Size mismatch");
    static_assert(std::is_trivially_copyable<TSource>::value, "TSource is not trivially copyable");
    static_assert(std::is_trivial<TTarget>::value, "TTarget is not trivial");

    // Support volatile qualifiers.
    // ReadUnaligned does not work with volatile pointers, so cast away
    // volatileness beforehand.
    using TNonvolatileSource = std::remove_volatile_t<TSource>;
    using TNonvolatileTarget = std::remove_volatile_t<TTarget>;

    return ReadUnaligned<TNonvolatileTarget>(&const_cast<const TNonvolatileSource&>(source));
}