Files
lix/src/libutil/charptr-cast.hh
T
Jade Lovelace a85c4ce535 tree-wide: automated migration to charptr_cast
The lint did it :3

Change-Id: I2d9f276b01ebbf14101de4257ea13e44ff6fe0a0
2024-08-08 14:53:17 -07:00

142 lines
4.6 KiB
C++

#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);
}
}