summaryrefslogtreecommitdiffstats
path: root/yql/essentials/utils/strong_alias.h
blob: bef73a37d0ebe102c5a37fdc178ac9d2e16425a0 (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
#pragma once

#include <util/generic/hash.h>
#include <util/stream/output.h>

#include <utility>

namespace NYql {

// A type-safe alternative to a typedef or a `using` alias.
//
// `using` aliases are transparent: two aliases of the same underlying type are
// interchangeable, so nothing stops values of different meaning (an id, an
// offset, a size) from being mixed up, compared, or passed to the wrong
// parameter:
//
//     using TUserId = ui64;
//     using TItemId = ui64;
//     TUserId userId = 1;
//     TItemId itemId = userId;      // compiles, but is almost certainly a bug
//     if (userId == itemId) { ... } // compiles, compares unrelated ids
//
// `TStrongAlias` fixes this by making every alias a distinct type, so prefer it
// over a typedef/`using` whenever the values represent different concepts even
// though they share the same underlying representation:
//
//     using TUserId = TStrongAlias<class TUserIdTag, ui64>;
//     using TItemId = TStrongAlias<class TItemIdTag, ui64>;
//     TUserId userId(1);
//     TItemId itemId = userId;       // does not compile
//     if (userId == itemId) { ... }  // does not compile
//     TUserId otherUserId = userId;  // compiles, same alias type
//
// `TTagType` is only ever used as a template parameter to distinguish
// instantiations from each other; an incomplete forward-declared type (as in
// the examples above) is enough, it never needs to be defined.
//
// `TStrongAlias` deliberately exposes only construction, comparison and access
// to the underlying value (via `Value()`, `operator*` and `operator->`) - not
// the full interface of `TUnderlyingType`. This is intentional: it prevents,
// for example, adding two ids together just because addition happens to be
// defined for the underlying integer type.
template <typename TTagType, typename TUnderlyingType>
class TStrongAlias {
public:
    using TUnderlying = TUnderlyingType;
    using TFuncParam = const std::remove_reference_t<TUnderlyingType>&;

    TStrongAlias() = default;

    constexpr explicit TStrongAlias(const TUnderlyingType& value)
        : Value_(value)
    {
    }

    constexpr explicit TStrongAlias(TUnderlyingType&& value) noexcept
        : Value_(std::move(value))
    {
    }

    constexpr TUnderlyingType* operator->() {
        return &Value_;
    }

    constexpr const TUnderlyingType* operator->() const {
        return &Value_;
    }

    constexpr TUnderlyingType& operator*() & {
        return Value_;
    }

    constexpr const TUnderlyingType& operator*() const& {
        return Value_;
    }

    constexpr TUnderlyingType&& operator*() && {
        return std::move(Value_);
    }

    constexpr const TUnderlyingType&& operator*() const&& {
        return std::move(Value_);
    }

    constexpr TUnderlyingType& Value() & {
        return Value_;
    }

    constexpr const TUnderlyingType& Value() const& {
        return Value_;
    }

    constexpr TUnderlyingType&& Value() && {
        return std::move(Value_);
    }

    constexpr const TUnderlyingType&& Value() const&& {
        return std::move(Value_);
    }

    constexpr explicit operator const TUnderlyingType&() const& {
        return Value_;
    }

    friend auto operator<=>(const TStrongAlias& lhs, const TStrongAlias& rhs) = default;
    friend bool operator==(const TStrongAlias& lhs, const TStrongAlias& rhs) = default;

private:
    TUnderlyingType Value_;
};

} // namespace NYql

template <typename TTagType, typename TUnderlyingType>
struct THash<NYql::TStrongAlias<TTagType, TUnderlyingType>> {
    size_t operator()(const NYql::TStrongAlias<TTagType, TUnderlyingType>& alias) const {
        return THash<TUnderlyingType>()(alias.Value());
    }
};

template <typename TTagType, typename TUnderlyingType>
struct TEqualTo<NYql::TStrongAlias<TTagType, TUnderlyingType>> {
    bool operator()(const NYql::TStrongAlias<TTagType, TUnderlyingType>& lhs,
                    const NYql::TStrongAlias<TTagType, TUnderlyingType>& rhs) const {
        return TEqualTo<TUnderlyingType>()(lhs.Value(), rhs.Value());
    }
};

template <typename TTagType, typename TUnderlyingType>
IOutputStream& operator<<(IOutputStream& out, const NYql::TStrongAlias<TTagType, TUnderlyingType>& value) {
    return out << value.Value();
}