aboutsummaryrefslogtreecommitdiff
path: root/src/libutil/charptr-cast.hh
blob: ec53d8924225a7910e531ed4bee4367f5f6b7864 (plain)
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
#pragma once
/** @file Safe casts between character pointer types. */

#include <concepts> // IWYU pragma: keep
#include <type_traits>

namespace nix {

namespace charptr_cast_detail {

/** Custom version of std::decay that does not eat CV qualifiers on \c {char * const}. */
template<typename T>
struct DecayArrayInternal
{
    using type = T;
};

template <typename T>
struct DecayArrayInternal<T[]>
{
    using type = T *;
};

template <typename T, std::size_t N>
struct DecayArrayInternal<T[N]>
{
    using type = T *;
};

template <typename T>
using DecayArray = DecayArrayInternal<T>::type;

/** Is a character type for the purposes of safe reinterpret_cast. */
template<typename T>
concept IsChar = std::same_as<T, char> || std::same_as<T, unsigned char>;

template<typename T>
concept IsConvertibleToChar = std::same_as<T, char8_t> || std::same_as<T, void> || IsChar<T>;

template<typename T>
concept IsDecayOrPointer = std::is_pointer_v<T> || std::is_pointer_v<DecayArray<T>>;

template<typename From, typename To>
concept ValidQualifiers = requires {
    // Does not discard const
    requires !std::is_const_v<From> || std::is_const_v<To>;
    // Don't deal with volatile
    requires !std::is_volatile_v<From> && !std::is_volatile_v<To>;
};

template<typename From, typename To>
concept BaseCase = requires {
    // Cannot cast away const
    requires ValidQualifiers<From, To>;
    // At base case, neither should be pointers
    requires !std::is_pointer_v<From> && !std::is_pointer_v<To>;
    // Finally are the types compatible?
    requires IsConvertibleToChar<std::remove_cv_t<From>>;
    requires IsChar<std::remove_cv_t<To>>;
};

static_assert(BaseCase<char, char>);
static_assert(BaseCase<unsigned char, char>);
static_assert(BaseCase<char8_t, char>);
static_assert(!BaseCase<const char8_t, char>);
static_assert(!BaseCase<const char8_t, unsigned char>);
static_assert(BaseCase<void, unsigned char>);
// Not legal to cast to char8_t
static_assert(!BaseCase<void, char8_t>);
// No pointers
static_assert(!BaseCase<void *, char8_t>);
static_assert(!BaseCase<char *, char *>);

// Required to be written in the old style because recursion in concepts is not
// allowed. Personally I think the committee hates fun.
template<typename From, typename To, typename = void>
struct RecursionHelper : std::false_type
{};

template<typename From, typename To>
struct RecursionHelper<From, To, std::enable_if_t<BaseCase<From, To>>> : std::true_type
{};

template<typename From, typename To>
struct RecursionHelper<
    From,
    To,
    std::enable_if_t<std::is_pointer_v<From> && std::is_pointer_v<To> && ValidQualifiers<From, To>>>
    : RecursionHelper<std::remove_pointer_t<From>, std::remove_pointer_t<To>>
{};

template<typename From, typename To>
concept IsCharCastable = requires {
    // We only decay arrays in From for safety reasons. There is almost no reason
    // to cast *into* an array and such code probably needs closer inspection
    // anyway.
    requires RecursionHelper<DecayArray<From>, To>::value;
    requires IsDecayOrPointer<From> && std::is_pointer_v<To>;
};

static_assert(!IsCharCastable<char **, char *>);
static_assert(IsCharCastable<char *, char *>);
static_assert(!IsCharCastable<const char *, char *>);
static_assert(!IsCharCastable<volatile char *, char *>);
static_assert(!IsCharCastable<char *, volatile char *>);
static_assert(IsCharCastable<char *, const char *>);
static_assert(IsCharCastable<char **, const char **>);
static_assert(IsCharCastable<char **, const char * const *>);
static_assert(!IsCharCastable<char * const *, const char **>);
static_assert(!IsCharCastable<char, char>);
static_assert(IsCharCastable<const char *, const unsigned char *>);
static_assert(!IsCharCastable<char [64][64], char **>);
static_assert(IsCharCastable<char [64], char *>);
}

/** Casts between character pointers with guaranteed safety. If this compiles,
 * it is at least a sound conversion per C++23 §7.2.1 line 11.
 *
 * This will not let you:
 * - Turn things into void *
 * - Turn things that are not char into char
 * - Turn things into things that are not char
 * - Cast away const
 *
 * At every level in the pointer indirections, \c To must as const or more
 * const than \c From.
 *
 * \c From may be any character pointer or void pointer or an array of characters.
 *
 * N.B. Be careful, the template args are in the possibly-surprising
 * order To, From due to deduction.
 */
template<typename To, typename From>
    requires charptr_cast_detail::IsCharCastable<From, To>
inline To charptr_cast(From p)
{
    // NOLINTNEXTLINE(lix-charptrcast): stop the linter ever getting too clever and causing funny recursion
    return reinterpret_cast<To>(p);
}

}