diff --git a/wpiutil/src/main/native/include/wpi/ct_string.h b/wpiutil/src/main/native/include/wpi/ct_string.h new file mode 100644 index 0000000000..3bf870d37a --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/ct_string.h @@ -0,0 +1,168 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include + +#include +#include +#include +#include +#include + +namespace wpi { + +// derived from: +// https://codereview.stackexchange.com/questions/282514/string-literals-concatenation-with-support-for-dynamic-strings + +/** + * Fixed length string (array of character) for compile time use. + * + * @tparam N number of characters + * @tparam Char character type + * @tparam Traits character traits + */ +template + requires(N < (std::numeric_limits::max)()) +struct ct_string { + std::array chars; + + template + requires(M <= (N + 1)) + consteval ct_string(Char const (&s)[M]) { // NOLINT + if constexpr (M == (N + 1)) { + if (s[N] != Char{}) { + throw std::logic_error{"char array not null terminated"}; + } + } + + // avoid dependency on + // auto p = std::ranges::copy(s, chars.begin()).out; + auto p = chars.begin(); + for (auto c : s) { + *p++ = c; + } + // std::ranges::fill() isn't constexpr on GCC 11 + while (p != chars.end()) { + *p++ = Char{}; + } + } + + explicit consteval ct_string(std::basic_string_view s) { + // avoid dependency on + // auto p = std::ranges::copy(s, chars.begin()).out; + auto p = chars.begin(); + for (auto c : s) { + *p++ = c; + } + // std::ranges::fill() isn't constexpr on GCC 11 + while (p != chars.end()) { + *p++ = Char{}; + } + } + + constexpr auto size() const noexcept { return N; } + + constexpr auto begin() const noexcept { return chars.begin(); } + constexpr auto end() const noexcept { return chars.begin() + N; } + + constexpr auto data() const noexcept { return chars.data(); } + constexpr auto c_str() const noexcept { return chars.data(); } + + constexpr operator std::basic_string_view() // NOLINT + const noexcept { + return std::basic_string_view{chars.data(), N}; + } +}; + +template +ct_string(Char const (&s)[M]) -> ct_string, M - 1>; + +namespace literals { +template +consteval auto operator""_ct_string() { + return S; +} +} // namespace literals + +template +consteval auto operator+(ct_string const& s1, + ct_string const& s2) noexcept { + return Concat(s1, s2); +} + +/** + * Concatenates multiple fixed_strings into a larger fixed_string at compile + * time. + * + * @param s1 first string + * @param s second and later strings + * @return concatenated string + */ +template +consteval auto Concat(ct_string const& s1, + ct_string const&... s) { + // Need a dummy array to instantiate a ct_string. + constexpr Char dummy[1] = {}; + auto res = ct_string{dummy}; + + auto p = res.chars.begin(); + auto append = [&p](auto&& s) { + // avoid dependency on + // p = std::ranges::copy(s, p).out; + for (auto c : s) { + *p++ = c; + } + }; + + (append(s1), ..., append(s)); + + return res; +} + +// derived from: +// https://github.com/tcsullivan/constexpr-to-string/blob/master/to_string.hpp + +/** + * Converts any integral to a ct_string at compile-time. + * + * @tparam N number to convert + * @tparam Base desired base, can be from 2 to 36 + * @tparam Char character type + * @tparam Traits character traits + */ +template > + requires(Base >= 2 && Base <= 36) +consteval auto NumToCtString() { + constexpr char digits[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + auto buflen = [] { + size_t len = N > 0 ? 0 : 1; + for (auto n = N; n; len++, n /= Base) { + } + return len; + }; + constexpr size_t size = buflen(); + + constexpr Char dummy[1] = {}; + auto res = ct_string{dummy}; + + auto ptr = res.chars.data() + size; + if (N != 0) { + for (auto n = N; n; n /= Base) { + *--ptr = digits[(N < 0 ? -1 : 1) * (n % Base)]; + } + if (N < 0) { + *--ptr = '-'; + } + } else { + res.chars[0] = '0'; + } + + return res; +} + +} // namespace wpi diff --git a/wpiutil/src/test/native/cpp/ct_string_test.cpp b/wpiutil/src/test/native/cpp/ct_string_test.cpp new file mode 100644 index 0000000000..df957478a1 --- /dev/null +++ b/wpiutil/src/test/native/cpp/ct_string_test.cpp @@ -0,0 +1,38 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "wpi/ct_string.h" // NOLINT(build/include_order) + +#include +#include + +#include "gtest/gtest.h" // NOLINT(build/include_order) + +TEST(CtStringTest, Concat) { + using namespace wpi::literals; + constexpr std::string_view astring = "name"; + constexpr int arrsize = 5; + constexpr auto str = Concat( + wpi::ct_string, astring.size()>{astring}, + "["_ct_string, wpi::NumToCtString(), "]"_ct_string); + static_assert(str.size() == 7); + ASSERT_EQ(std::string{str}, "name[5]"); +} + +TEST(CtStringTest, OperatorPlus) { + using namespace wpi::literals; + constexpr std::string_view astring = "name"; + constexpr auto str = + wpi::ct_string, astring.size()>{astring} + + "[]"_ct_string; + static_assert(str.size() == 6); + ASSERT_EQ(std::string{str}, "name[]"); +} + +TEST(CtStringTest, StringViewConversion) { + using namespace wpi::literals; + constexpr auto str = "[]"_ct_string; + std::string_view sv = str; + ASSERT_EQ(sv, "[]"); +}