#pragma once /** @file Safe casts between character pointer types. */ #include // IWYU pragma: keep #include namespace nix { namespace charptr_cast_detail { /** Custom version of std::decay that does not eat CV qualifiers on \c {char * const}. */ template struct DecayArrayInternal { using type = T; }; template struct DecayArrayInternal { using type = T *; }; template struct DecayArrayInternal { using type = T *; }; template using DecayArray = DecayArrayInternal::type; /** Is a character type for the purposes of safe reinterpret_cast. */ template concept IsChar = std::same_as || std::same_as; template concept IsConvertibleToChar = std::same_as || std::same_as || IsChar; template concept IsDecayOrPointer = std::is_pointer_v || std::is_pointer_v>; template concept ValidQualifiers = requires { // Does not discard const requires !std::is_const_v || std::is_const_v; // Don't deal with volatile requires !std::is_volatile_v && !std::is_volatile_v; }; template concept BaseCase = requires { // Cannot cast away const requires ValidQualifiers; // At base case, neither should be pointers requires !std::is_pointer_v && !std::is_pointer_v; // Finally are the types compatible? requires IsConvertibleToChar>; requires IsChar>; }; static_assert(BaseCase); static_assert(BaseCase); static_assert(BaseCase); static_assert(!BaseCase); static_assert(!BaseCase); static_assert(BaseCase); // Not legal to cast to char8_t static_assert(!BaseCase); // No pointers static_assert(!BaseCase); static_assert(!BaseCase); // Required to be written in the old style because recursion in concepts is not // allowed. Personally I think the committee hates fun. template struct RecursionHelper : std::false_type {}; template struct RecursionHelper>> : std::true_type {}; template struct RecursionHelper< From, To, std::enable_if_t && std::is_pointer_v && ValidQualifiers>> : RecursionHelper, std::remove_pointer_t> {}; template 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, To>::value; requires IsDecayOrPointer && std::is_pointer_v; }; static_assert(!IsCharCastable); static_assert(IsCharCastable); static_assert(!IsCharCastable); static_assert(!IsCharCastable); static_assert(!IsCharCastable); static_assert(IsCharCastable); static_assert(IsCharCastable); static_assert(IsCharCastable); static_assert(!IsCharCastable); static_assert(!IsCharCastable); static_assert(IsCharCastable); static_assert(!IsCharCastable); static_assert(IsCharCastable); } /** 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 requires charptr_cast_detail::IsCharCastable inline To charptr_cast(From p) { // NOLINTNEXTLINE(lix-charptrcast): stop the linter ever getting too clever and causing funny recursion return reinterpret_cast(p); } }