about summary refs log tree commit diff
path: root/absl/strings/internal
diff options
context:
space:
mode:
Diffstat (limited to 'absl/strings/internal')
-rw-r--r--absl/strings/internal/cord_internal.h151
-rw-r--r--absl/strings/internal/str_format/arg.cc37
-rw-r--r--absl/strings/internal/str_format/arg.h7
-rw-r--r--absl/strings/internal/str_format/arg_test.cc2
-rw-r--r--absl/strings/internal/str_format/extension.cc11
-rw-r--r--absl/strings/internal/str_format/extension.h200
-rw-r--r--absl/strings/internal/str_format/float_conversion.cc18
-rw-r--r--absl/strings/internal/str_format/parser.cc6
-rw-r--r--absl/strings/internal/str_format/parser.h10
-rw-r--r--absl/strings/internal/str_format/parser_test.cc14
10 files changed, 305 insertions, 151 deletions
diff --git a/absl/strings/internal/cord_internal.h b/absl/strings/internal/cord_internal.h
new file mode 100644
index 000000000000..5b5d10830891
--- /dev/null
+++ b/absl/strings/internal/cord_internal.h
@@ -0,0 +1,151 @@
+// Copyright 2020 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_STRINGS_INTERNAL_CORD_INTERNAL_H_
+#define ABSL_STRINGS_INTERNAL_CORD_INTERNAL_H_
+
+#include <atomic>
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <type_traits>
+
+#include "absl/meta/type_traits.h"
+#include "absl/strings/string_view.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace cord_internal {
+
+// Wraps std::atomic for reference counting.
+class Refcount {
+ public:
+  Refcount() : count_{1} {}
+  ~Refcount() {}
+
+  // Increments the reference count by 1. Imposes no memory ordering.
+  inline void Increment() { count_.fetch_add(1, std::memory_order_relaxed); }
+
+  // Asserts that the current refcount is greater than 0. If the refcount is
+  // greater than 1, decrements the reference count by 1.
+  //
+  // Returns false if there are no references outstanding; true otherwise.
+  // Inserts barriers to ensure that state written before this method returns
+  // false will be visible to a thread that just observed this method returning
+  // false.
+  inline bool Decrement() {
+    int32_t refcount = count_.load(std::memory_order_acquire);
+    assert(refcount > 0);
+    return refcount != 1 && count_.fetch_sub(1, std::memory_order_acq_rel) != 1;
+  }
+
+  // Same as Decrement but expect that refcount is greater than 1.
+  inline bool DecrementExpectHighRefcount() {
+    int32_t refcount = count_.fetch_sub(1, std::memory_order_acq_rel);
+    assert(refcount > 0);
+    return refcount != 1;
+  }
+
+  // Returns the current reference count using acquire semantics.
+  inline int32_t Get() const { return count_.load(std::memory_order_acquire); }
+
+  // Returns whether the atomic integer is 1.
+  // If the reference count is used in the conventional way, a
+  // reference count of 1 implies that the current thread owns the
+  // reference and no other thread shares it.
+  // This call performs the test for a reference count of one, and
+  // performs the memory barrier needed for the owning thread
+  // to act on the object, knowing that it has exclusive access to the
+  // object.
+  inline bool IsOne() { return count_.load(std::memory_order_acquire) == 1; }
+
+ private:
+  std::atomic<int32_t> count_;
+};
+
+// The overhead of a vtable is too much for Cord, so we roll our own subclasses
+// using only a single byte to differentiate classes from each other - the "tag"
+// byte.  Define the subclasses first so we can provide downcasting helper
+// functions in the base class.
+
+struct CordRepConcat;
+struct CordRepSubstring;
+struct CordRepExternal;
+
+struct CordRep {
+  // The following three fields have to be less than 32 bytes since
+  // that is the smallest supported flat node size.
+  // We use uint64_t for the length even in 32-bit binaries.
+  uint64_t length;
+  Refcount refcount;
+  // If tag < FLAT, it represents CordRepKind and indicates the type of node.
+  // Otherwise, the node type is CordRepFlat and the tag is the encoded size.
+  uint8_t tag;
+  char data[1];  // Starting point for flat array: MUST BE LAST FIELD of CordRep
+
+  inline CordRepConcat* concat();
+  inline const CordRepConcat* concat() const;
+  inline CordRepSubstring* substring();
+  inline const CordRepSubstring* substring() const;
+  inline CordRepExternal* external();
+  inline const CordRepExternal* external() const;
+};
+
+struct CordRepConcat : public CordRep {
+  CordRep* left;
+  CordRep* right;
+
+  uint8_t depth() const { return static_cast<uint8_t>(data[0]); }
+  void set_depth(uint8_t depth) { data[0] = static_cast<char>(depth); }
+};
+
+struct CordRepSubstring : public CordRep {
+  size_t start;  // Starting offset of substring in child
+  CordRep* child;
+};
+
+// TODO(strel): replace the following logic (and related functions in cord.cc)
+// with container_internal::Layout.
+
+// Alignment requirement for CordRepExternal so that the type erased releaser
+// will be stored at a suitably aligned address.
+constexpr size_t ExternalRepAlignment() {
+#if defined(__STDCPP_DEFAULT_NEW_ALIGNMENT__)
+  return __STDCPP_DEFAULT_NEW_ALIGNMENT__;
+#else
+  return alignof(max_align_t);
+#endif
+}
+
+// Type for function pointer that will invoke and destroy the type-erased
+// releaser function object. Accepts a pointer to the releaser and the
+// `string_view` that were passed in to `NewExternalRep` below. The return value
+// is the size of the `Releaser` type.
+using ExternalReleaserInvoker = size_t (*)(void*, absl::string_view);
+
+// External CordReps are allocated together with a type erased releaser. The
+// releaser is stored in the memory directly following the CordRepExternal.
+struct alignas(ExternalRepAlignment()) CordRepExternal : public CordRep {
+  const char* base;
+  // Pointer to function that knows how to call and destroy the releaser.
+  ExternalReleaserInvoker releaser_invoker;
+};
+
+// TODO(strel): look into removing, it doesn't seem like anything relies on this
+static_assert(sizeof(CordRepConcat) == sizeof(CordRepSubstring), "");
+
+}  // namespace cord_internal
+ABSL_NAMESPACE_END
+}  // namespace absl
+#endif  // ABSL_STRINGS_INTERNAL_CORD_INTERNAL_H_
diff --git a/absl/strings/internal/str_format/arg.cc b/absl/strings/internal/str_format/arg.cc
index d3904124a1a9..4d0604e00c4b 100644
--- a/absl/strings/internal/str_format/arg.cc
+++ b/absl/strings/internal/str_format/arg.cc
@@ -88,7 +88,7 @@ class ConvertedIntInfo {
   template <typename T>
   void UnsignedToStringRight(T u, ConversionChar conv) {
     char *p = end();
-    switch (conv.radix()) {
+    switch (FormatConversionCharRadix(conv)) {
       default:
       case 10:
         for (; u; u /= 10)
@@ -99,7 +99,7 @@ class ConvertedIntInfo {
           *--p = static_cast<char>('0' + static_cast<size_t>(u % 8));
         break;
       case 16: {
-        const char *digits = kDigit[conv.upper() ? 1 : 0];
+        const char *digits = kDigit[FormatConversionCharIsUpper(conv) ? 1 : 0];
         for (; u; u /= 16) *--p = digits[static_cast<size_t>(u % 16)];
         break;
       }
@@ -121,21 +121,20 @@ class ConvertedIntInfo {
 string_view BaseIndicator(const ConvertedIntInfo &info,
                           const ConversionSpec conv) {
   bool alt = conv.flags().alt;
-  int radix = conv.conv().radix();
-  if (conv.conv().id() == ConversionChar::p)
-    alt = true;  // always show 0x for %p.
+  int radix = FormatConversionCharRadix(conv.conv());
+  if (conv.conv() == ConversionChar::p) alt = true;  // always show 0x for %p.
   // From the POSIX description of '#' flag:
   //   "For x or X conversion specifiers, a non-zero result shall have
   //   0x (or 0X) prefixed to it."
   if (alt && radix == 16 && !info.digits().empty()) {
-    if (conv.conv().upper()) return "0X";
+    if (FormatConversionCharIsUpper(conv.conv())) return "0X";
     return "0x";
   }
   return {};
 }
 
 string_view SignColumn(bool neg, const ConversionSpec conv) {
-  if (conv.conv().is_signed()) {
+  if (FormatConversionCharIsSigned(conv.conv())) {
     if (neg) return "-";
     if (conv.flags().show_pos) return "+";
     if (conv.flags().sign_col) return " ";
@@ -175,7 +174,7 @@ bool ConvertIntImplInner(const ConvertedIntInfo &info,
   if (!precision_specified)
     precision = 1;
 
-  if (conv.flags().alt && conv.conv().id() == ConversionChar::o) {
+  if (conv.flags().alt && conv.conv() == ConversionChar::o) {
     // From POSIX description of the '#' (alt) flag:
     //   "For o conversion, it increases the precision (if necessary) to
     //   force the first digit of the result to be zero."
@@ -211,7 +210,7 @@ bool ConvertIntImplInner(const ConvertedIntInfo &info,
 template <typename T>
 bool ConvertIntImplInner(T v, const ConversionSpec conv, FormatSinkImpl *sink) {
   ConvertedIntInfo info(v, conv.conv());
-  if (conv.flags().basic && conv.conv().id() != ConversionChar::p) {
+  if (conv.flags().basic && (conv.conv() != ConversionChar::p)) {
     if (info.is_neg()) sink->Append(1, '-');
     if (info.digits().empty()) {
       sink->Append(1, '0');
@@ -225,14 +224,13 @@ bool ConvertIntImplInner(T v, const ConversionSpec conv, FormatSinkImpl *sink) {
 
 template <typename T>
 bool ConvertIntArg(T v, const ConversionSpec conv, FormatSinkImpl *sink) {
-  if (conv.conv().is_float()) {
+  if (FormatConversionCharIsFloat(conv.conv())) {
     return FormatConvertImpl(static_cast<double>(v), conv, sink).value;
   }
-  if (conv.conv().id() == ConversionChar::c)
+  if (conv.conv() == ConversionChar::c)
     return ConvertCharImpl(static_cast<unsigned char>(v), conv, sink);
-  if (!conv.conv().is_integral())
-    return false;
-  if (!conv.conv().is_signed() && IsSigned<T>::value) {
+  if (!FormatConversionCharIsIntegral(conv.conv())) return false;
+  if (!FormatConversionCharIsSigned(conv.conv()) && IsSigned<T>::value) {
     using U = typename MakeUnsigned<T>::type;
     return FormatConvertImpl(static_cast<U>(v), conv, sink).value;
   }
@@ -241,13 +239,13 @@ bool ConvertIntArg(T v, const ConversionSpec conv, FormatSinkImpl *sink) {
 
 template <typename T>
 bool ConvertFloatArg(T v, const ConversionSpec conv, FormatSinkImpl *sink) {
-  return conv.conv().is_float() && ConvertFloatImpl(v, conv, sink);
+  return FormatConversionCharIsFloat(conv.conv()) &&
+         ConvertFloatImpl(v, conv, sink);
 }
 
 inline bool ConvertStringArg(string_view v, const ConversionSpec conv,
                              FormatSinkImpl *sink) {
-  if (conv.conv().id() != ConversionChar::s)
-    return false;
+  if (conv.conv() != ConversionChar::s) return false;
   if (conv.flags().basic) {
     sink->Append(v);
     return true;
@@ -274,7 +272,7 @@ ConvertResult<Conv::s> FormatConvertImpl(string_view v,
 ConvertResult<Conv::s | Conv::p> FormatConvertImpl(const char *v,
                                                    const ConversionSpec conv,
                                                    FormatSinkImpl *sink) {
-  if (conv.conv().id() == ConversionChar::p)
+  if (conv.conv() == ConversionChar::p)
     return {FormatConvertImpl(VoidPtr(v), conv, sink).value};
   size_t len;
   if (v == nullptr) {
@@ -291,8 +289,7 @@ ConvertResult<Conv::s | Conv::p> FormatConvertImpl(const char *v,
 // ==================== Raw pointers ====================
 ConvertResult<Conv::p> FormatConvertImpl(VoidPtr v, const ConversionSpec conv,
                                          FormatSinkImpl *sink) {
-  if (conv.conv().id() != ConversionChar::p)
-    return {false};
+  if (conv.conv() != ConversionChar::p) return {false};
   if (!v.value) {
     sink->Append("(nil)");
     return {true};
diff --git a/absl/strings/internal/str_format/arg.h b/absl/strings/internal/str_format/arg.h
index b672a2294416..7a93756305e4 100644
--- a/absl/strings/internal/str_format/arg.h
+++ b/absl/strings/internal/str_format/arg.h
@@ -70,7 +70,7 @@ template <class AbslCord,
 ConvertResult<Conv::s> FormatConvertImpl(const AbslCord& value,
                                          ConversionSpec conv,
                                          FormatSinkImpl* sink) {
-  if (conv.conv().id() != ConversionChar::s) return {false};
+  if (conv.conv() != ConversionChar::s) return {false};
 
   bool is_left = conv.flags().left;
   size_t space_remaining = 0;
@@ -185,8 +185,7 @@ struct FormatCountCaptureHelper {
                                               FormatSinkImpl* sink) {
     const absl::enable_if_t<sizeof(T) != 0, FormatCountCapture>& v2 = v;
 
-    if (conv.conv().id() != str_format_internal::ConversionChar::n)
-      return {false};
+    if (conv.conv() != str_format_internal::ConversionChar::n) return {false};
     *v2.p_ = static_cast<int>(sink->size());
     return {true};
   }
@@ -378,7 +377,7 @@ class FormatArgImpl {
   template <typename T>
   static bool Dispatch(Data arg, ConversionSpec spec, void* out) {
     // A `none` conv indicates that we want the `int` conversion.
-    if (ABSL_PREDICT_FALSE(spec.conv().id() == ConversionChar::none)) {
+    if (ABSL_PREDICT_FALSE(spec.conv() == ConversionChar::none)) {
       return ToInt<T>(arg, static_cast<int*>(out), std::is_integral<T>(),
                       std::is_enum<T>());
     }
diff --git a/absl/strings/internal/str_format/arg_test.cc b/absl/strings/internal/str_format/arg_test.cc
index 96c9cfd335b5..04fa56cd3623 100644
--- a/absl/strings/internal/str_format/arg_test.cc
+++ b/absl/strings/internal/str_format/arg_test.cc
@@ -96,7 +96,7 @@ TEST_F(FormatArgImplTest, WorksWithCharArraysOfUnknownSize) {
   std::string s;
   FormatSinkImpl sink(&s);
   ConversionSpec conv;
-  conv.set_conv(ConversionChar::FromChar('s'));
+  conv.set_conv(ConversionChar::s);
   conv.set_flags(Flags());
   conv.set_width(-1);
   conv.set_precision(-1);
diff --git a/absl/strings/internal/str_format/extension.cc b/absl/strings/internal/str_format/extension.cc
index 21688e873f2c..2e5bc2ce0be0 100644
--- a/absl/strings/internal/str_format/extension.cc
+++ b/absl/strings/internal/str_format/extension.cc
@@ -23,15 +23,6 @@ namespace absl {
 ABSL_NAMESPACE_BEGIN
 namespace str_format_internal {
 
-const ConversionChar::Spec ConversionChar::kSpecs[] = {
-#define X_VAL(id) { ConversionChar::id, #id[0] }
-#define X_SEP ,
-    ABSL_CONVERSION_CHARS_EXPAND_(X_VAL, X_SEP),
-    {ConversionChar::none, '\0'},
-#undef X_VAL
-#undef X_SEP
-};
-
 std::string Flags::ToString() const {
   std::string s;
   s.append(left     ? "-" : "");
@@ -42,8 +33,6 @@ std::string Flags::ToString() const {
   return s;
 }
 
-const size_t ConversionChar::kNumValues;
-
 bool FormatSinkImpl::PutPaddedString(string_view v, int w, int p, bool l) {
   size_t space_remaining = 0;
   if (w >= 0) space_remaining = w;
diff --git a/absl/strings/internal/str_format/extension.h b/absl/strings/internal/str_format/extension.h
index 4868eac3e8b4..1a863c207bb1 100644
--- a/absl/strings/internal/str_format/extension.h
+++ b/absl/strings/internal/str_format/extension.h
@@ -148,117 +148,122 @@ struct Flags {
   X_VAL(g) X_SEP X_VAL(G) X_SEP X_VAL(a) X_SEP X_VAL(A) X_SEP \
   /* misc */ \
   X_VAL(n) X_SEP X_VAL(p)
-// clang-format on
 
-struct ABSL_DLL ConversionChar {
- public:
-  enum Id : uint8_t {
+enum class FormatConversionChar : uint8_t {
     c, C, s, S,              // text
     d, i, o, u, x, X,        // int
     f, F, e, E, g, G, a, A,  // float
     n, p,                    // misc
-    none
-  };
-  static const size_t kNumValues = none + 1;
-
-  ConversionChar() : id_(none) {}
-
- public:
-  // Index into the opaque array of ConversionChar enums.
-  // Requires: i < kNumValues
-  static ConversionChar FromIndex(size_t i) {
-    return ConversionChar(kSpecs[i].value);
-  }
+    kNone,
+    none = kNone
+};
+// clang-format on
 
-  static ConversionChar FromChar(char c) {
-    ConversionChar::Id out_id = ConversionChar::none;
-    switch (c) {
-#define X_VAL(id)                \
-  case #id[0]:                   \
-    out_id = ConversionChar::id; \
-    break;
-      ABSL_CONVERSION_CHARS_EXPAND_(X_VAL, )
+inline FormatConversionChar FormatConversionCharFromChar(char c) {
+  switch (c) {
+#define X_VAL(id) \
+  case #id[0]:    \
+    return FormatConversionChar::id;
+    ABSL_CONVERSION_CHARS_EXPAND_(X_VAL, )
 #undef X_VAL
-      default:
-        break;
-    }
-    return ConversionChar(out_id);
   }
+  return FormatConversionChar::kNone;
+}
 
-  static ConversionChar FromId(Id id) { return ConversionChar(id); }
-  Id id() const { return id_; }
-
-  int radix() const {
-    switch (id()) {
-      case x: case X: case a: case A: case p: return 16;
-      case o: return 8;
-      default: return 10;
-    }
+inline int FormatConversionCharRadix(FormatConversionChar c) {
+  switch (c) {
+    case FormatConversionChar::x:
+    case FormatConversionChar::X:
+    case FormatConversionChar::a:
+    case FormatConversionChar::A:
+    case FormatConversionChar::p:
+      return 16;
+    case FormatConversionChar::o:
+      return 8;
+    default:
+      return 10;
   }
+}
 
-  bool upper() const {
-    switch (id()) {
-      case X: case F: case E: case G: case A: return true;
-      default: return false;
-    }
+inline bool FormatConversionCharIsUpper(FormatConversionChar c) {
+  switch (c) {
+    case FormatConversionChar::X:
+    case FormatConversionChar::F:
+    case FormatConversionChar::E:
+    case FormatConversionChar::G:
+    case FormatConversionChar::A:
+      return true;
+    default:
+      return false;
   }
+}
 
-  bool is_signed() const {
-    switch (id()) {
-      case d: case i: return true;
-      default: return false;
-    }
+inline bool FormatConversionCharIsSigned(FormatConversionChar c) {
+  switch (c) {
+    case FormatConversionChar::d:
+    case FormatConversionChar::i:
+      return true;
+    default:
+      return false;
   }
+}
 
-  bool is_integral() const {
-    switch (id()) {
-      case d: case i: case u: case o: case x: case X:
-        return true;
-      default: return false;
-    }
+inline bool FormatConversionCharIsIntegral(FormatConversionChar c) {
+  switch (c) {
+    case FormatConversionChar::d:
+    case FormatConversionChar::i:
+    case FormatConversionChar::u:
+    case FormatConversionChar::o:
+    case FormatConversionChar::x:
+    case FormatConversionChar::X:
+      return true;
+    default:
+      return false;
   }
+}
 
-  bool is_float() const {
-    switch (id()) {
-      case a: case e: case f: case g: case A: case E: case F: case G:
-        return true;
-      default: return false;
-    }
+inline bool FormatConversionCharIsFloat(FormatConversionChar c) {
+  switch (c) {
+    case FormatConversionChar::a:
+    case FormatConversionChar::e:
+    case FormatConversionChar::f:
+    case FormatConversionChar::g:
+    case FormatConversionChar::A:
+    case FormatConversionChar::E:
+    case FormatConversionChar::F:
+    case FormatConversionChar::G:
+      return true;
+    default:
+      return false;
   }
+}
 
-  bool IsValid() const { return id() != none; }
-
-  // The associated char.
-  char Char() const { return kSpecs[id_].name; }
-
-  friend bool operator==(const ConversionChar& a, const ConversionChar& b) {
-    return a.id() == b.id();
-  }
-  friend bool operator!=(const ConversionChar& a, const ConversionChar& b) {
-    return !(a == b);
-  }
-  friend std::ostream& operator<<(std::ostream& os, const ConversionChar& v) {
-    char c = v.Char();
-    if (!c) c = '?';
-    return os << c;
+inline char FormatConversionCharToChar(FormatConversionChar c) {
+  switch (c) {
+#define X_VAL(e)                \
+  case FormatConversionChar::e: \
+    return #e[0];
+#define X_SEP
+    ABSL_CONVERSION_CHARS_EXPAND_(X_VAL, X_SEP)
+    case FormatConversionChar::kNone:
+      return '\0';
+#undef X_VAL
+#undef X_SEP
   }
+  return '\0';
+}
 
- private:
-  struct Spec {
-    Id value;
-    char name;
-  };
-  static const Spec kSpecs[];
-
-  explicit ConversionChar(Id id) : id_(id) {}
-
-  Id id_;
-};
+// The associated char.
+inline std::ostream& operator<<(std::ostream& os, FormatConversionChar v) {
+  char c = FormatConversionCharToChar(v);
+  if (!c) c = '?';
+  return os << c;
+}
 
 class ConversionSpec {
  public:
   Flags flags() const { return flags_; }
-  ConversionChar conv() const {
+  FormatConversionChar conv() const {
     // Keep this field first in the struct . It generates better code when
     // accessing it when ConversionSpec is passed by value in registers.
     static_assert(offsetof(ConversionSpec, conv_) == 0, "");
@@ -273,22 +278,24 @@ class ConversionSpec {
   int precision() const { return precision_; }
 
   void set_flags(Flags f) { flags_ = f; }
-  void set_conv(ConversionChar c) { conv_ = c; }
+  void set_conv(FormatConversionChar c) { conv_ = c; }
   void set_width(int w) { width_ = w; }
   void set_precision(int p) { precision_ = p; }
   void set_left(bool b) { flags_.left = b; }
 
  private:
-  ConversionChar conv_;
+  FormatConversionChar conv_ = FormatConversionChar::kNone;
   Flags flags_;
   int width_;
   int precision_;
 };
 
-constexpr uint64_t ConversionCharToConvValue(char conv) {
+constexpr uint64_t FormatConversionCharToConvValue(char conv) {
   return
-#define CONV_SET_CASE(c) \
-  conv == #c[0] ? (uint64_t{1} << (1 + ConversionChar::Id::c)):
+#define CONV_SET_CASE(c)                                                     \
+  conv == #c[0]                                                              \
+      ? (uint64_t{1} << (1 + static_cast<uint8_t>(FormatConversionChar::c))) \
+      :
       ABSL_CONVERSION_CHARS_EXPAND_(CONV_SET_CASE, )
 #undef CONV_SET_CASE
                   conv == '*'
@@ -297,12 +304,12 @@ constexpr uint64_t ConversionCharToConvValue(char conv) {
 }
 
 enum class Conv : uint64_t {
-#define CONV_SET_CASE(c) c = ConversionCharToConvValue(#c[0]),
+#define CONV_SET_CASE(c) c = FormatConversionCharToConvValue(#c[0]),
   ABSL_CONVERSION_CHARS_EXPAND_(CONV_SET_CASE, )
 #undef CONV_SET_CASE
 
   // Used for width/precision '*' specification.
-  star = ConversionCharToConvValue('*'),
+  star = FormatConversionCharToConvValue('*'),
 
   // Some predefined values:
   integral = d | i | u | o | x | X,
@@ -323,12 +330,12 @@ constexpr Conv operator|(Conv a, Conv b) {
 
 // Get a conversion with a single character in it.
 constexpr Conv ConversionCharToConv(char c) {
-  return Conv(ConversionCharToConvValue(c));
+  return Conv(FormatConversionCharToConvValue(c));
 }
 
 // Checks whether `c` exists in `set`.
 constexpr bool Contains(Conv set, char c) {
-  return (static_cast<uint64_t>(set) & ConversionCharToConvValue(c)) != 0;
+  return (static_cast<uint64_t>(set) & FormatConversionCharToConvValue(c)) != 0;
 }
 
 // Checks whether all the characters in `c` are contained in `set`
@@ -353,6 +360,9 @@ inline size_t Excess(size_t used, size_t capacity) {
   return used < capacity ? capacity - used : 0;
 }
 
+// Type alias for use during migration.
+using ConversionChar = FormatConversionChar;
+
 }  // namespace str_format_internal
 
 ABSL_NAMESPACE_END
diff --git a/absl/strings/internal/str_format/float_conversion.cc b/absl/strings/internal/str_format/float_conversion.cc
index ebe4da5b4233..c98ed4ba5008 100644
--- a/absl/strings/internal/str_format/float_conversion.cc
+++ b/absl/strings/internal/str_format/float_conversion.cc
@@ -33,7 +33,7 @@ bool FallbackToSnprintf(const Float v, const ConversionSpec &conv,
     if (std::is_same<long double, Float>()) {
       *fp++ = 'L';
     }
-    *fp++ = conv.conv().Char();
+    *fp++ = FormatConversionCharToChar(conv.conv());
     *fp = 0;
     assert(fp < fmt + sizeof(fmt));
   }
@@ -100,9 +100,11 @@ bool ConvertNonNumericFloats(char sign_char, Float v,
   char text[4], *ptr = text;
   if (sign_char) *ptr++ = sign_char;
   if (std::isnan(v)) {
-    ptr = std::copy_n(conv.conv().upper() ? "NAN" : "nan", 3, ptr);
+    ptr = std::copy_n(FormatConversionCharIsUpper(conv.conv()) ? "NAN" : "nan",
+                      3, ptr);
   } else if (std::isinf(v)) {
-    ptr = std::copy_n(conv.conv().upper() ? "INF" : "inf", 3, ptr);
+    ptr = std::copy_n(FormatConversionCharIsUpper(conv.conv()) ? "INF" : "inf",
+                      3, ptr);
   } else {
     return false;
   }
@@ -399,7 +401,7 @@ bool FloatToSink(const Float v, const ConversionSpec &conv,
 
   Buffer buffer;
 
-  switch (conv.conv().id()) {
+  switch (conv.conv()) {
     case ConversionChar::f:
     case ConversionChar::F:
       if (!FloatToBuffer<FormatStyle::Fixed>(decomposed, precision, &buffer,
@@ -416,7 +418,8 @@ bool FloatToSink(const Float v, const ConversionSpec &conv,
         return FallbackToSnprintf(v, conv, sink);
       }
       if (!conv.flags().alt && buffer.back() == '.') buffer.pop_back();
-      PrintExponent(exp, conv.conv().upper() ? 'E' : 'e', &buffer);
+      PrintExponent(exp, FormatConversionCharIsUpper(conv.conv()) ? 'E' : 'e',
+                    &buffer);
       break;
 
     case ConversionChar::g:
@@ -447,7 +450,10 @@ bool FloatToSink(const Float v, const ConversionSpec &conv,
         while (buffer.back() == '0') buffer.pop_back();
         if (buffer.back() == '.') buffer.pop_back();
       }
-      if (exp) PrintExponent(exp, conv.conv().upper() ? 'E' : 'e', &buffer);
+      if (exp) {
+        PrintExponent(exp, FormatConversionCharIsUpper(conv.conv()) ? 'E' : 'e',
+                      &buffer);
+      }
       break;
 
     case ConversionChar::a:
diff --git a/absl/strings/internal/str_format/parser.cc b/absl/strings/internal/str_format/parser.cc
index c4b527bc48aa..aab68db94bca 100644
--- a/absl/strings/internal/str_format/parser.cc
+++ b/absl/strings/internal/str_format/parser.cc
@@ -17,7 +17,7 @@ namespace absl {
 ABSL_NAMESPACE_BEGIN
 namespace str_format_internal {
 
-using CC = ConversionChar::Id;
+using CC = ConversionChar;
 using LM = LengthMod;
 
 ABSL_CONST_INIT const ConvTag kTags[256] = {
@@ -322,7 +322,9 @@ bool ParsedFormatBase::MatchesConversions(
     if (conv.width.is_from_arg() &&
         !add_if_valid_conv(conv.width.get_from_arg(), '*'))
       return false;
-    if (!add_if_valid_conv(conv.arg_position, conv.conv.Char())) return false;
+    if (!add_if_valid_conv(conv.arg_position,
+                           FormatConversionCharToChar(conv.conv)))
+      return false;
   }
   return used.size() == convs.size() || allow_ignored;
 }
diff --git a/absl/strings/internal/str_format/parser.h b/absl/strings/internal/str_format/parser.h
index 6cbe257697e4..45c90d1df072 100644
--- a/absl/strings/internal/str_format/parser.h
+++ b/absl/strings/internal/str_format/parser.h
@@ -67,7 +67,7 @@ struct UnboundConversion {
 
   Flags flags;
   LengthMod length_mod = LengthMod::none;
-  ConversionChar conv;
+  ConversionChar conv = FormatConversionChar::kNone;
 };
 
 // Consume conversion spec prefix (not including '%') of [p, end) if valid.
@@ -79,10 +79,12 @@ const char* ConsumeUnboundConversion(const char* p, const char* end,
                                      UnboundConversion* conv, int* next_arg);
 
 // Helper tag class for the table below.
-// It allows fast `char -> ConversionChar/LengthMod` checking and conversions.
+// It allows fast `char -> ConversionChar/LengthMod` checking and
+// conversions.
 class ConvTag {
  public:
-  constexpr ConvTag(ConversionChar::Id id) : tag_(id) {}  // NOLINT
+  constexpr ConvTag(ConversionChar conversion_char)  // NOLINT
+      : tag_(static_cast<int8_t>(conversion_char)) {}
   // We invert the length modifiers to make them negative so that we can easily
   // test for them.
   constexpr ConvTag(LengthMod length_mod)  // NOLINT
@@ -94,7 +96,7 @@ class ConvTag {
   bool is_length() const { return tag_ < 0 && tag_ != -128; }
   ConversionChar as_conv() const {
     assert(is_conv());
-    return ConversionChar::FromId(static_cast<ConversionChar::Id>(tag_));
+    return static_cast<ConversionChar>(tag_);
   }
   LengthMod as_length() const {
     assert(is_length());
diff --git a/absl/strings/internal/str_format/parser_test.cc b/absl/strings/internal/str_format/parser_test.cc
index 4a8efd0805aa..1b1ee030f183 100644
--- a/absl/strings/internal/str_format/parser_test.cc
+++ b/absl/strings/internal/str_format/parser_test.cc
@@ -41,7 +41,7 @@ TEST(LengthModTest, Names) {
 
 TEST(ConversionCharTest, Names) {
   struct Expectation {
-    ConversionChar::Id id;
+    ConversionChar id;
     char name;
   };
   // clang-format off
@@ -55,12 +55,10 @@ TEST(ConversionCharTest, Names) {
     {ConversionChar::none, '\0'},
   };
   // clang-format on
-  EXPECT_EQ(ABSL_ARRAYSIZE(kExpect), ConversionChar::kNumValues);
   for (auto e : kExpect) {
     SCOPED_TRACE(e.name);
-    ConversionChar v = ConversionChar::FromId(e.id);
-    EXPECT_EQ(e.id, v.id());
-    EXPECT_EQ(e.name, v.Char());
+    ConversionChar v = e.id;
+    EXPECT_EQ(e.name, FormatConversionCharToChar(v));
   }
 }
 
@@ -119,7 +117,7 @@ TEST_F(ConsumeUnboundConversionTest, BasicConversion) {
   EXPECT_FALSE(Run("dd"));  // no excess allowed
 
   EXPECT_TRUE(Run("d"));
-  EXPECT_EQ('d', o.conv.Char());
+  EXPECT_EQ('d', FormatConversionCharToChar(o.conv));
   EXPECT_FALSE(o.width.is_from_arg());
   EXPECT_LT(o.width.value(), 0);
   EXPECT_FALSE(o.precision.is_from_arg());
@@ -160,7 +158,7 @@ TEST_F(ConsumeUnboundConversionTest, ArgPosition) {
 
 TEST_F(ConsumeUnboundConversionTest, WidthAndPrecision) {
   EXPECT_TRUE(Run("14d"));
-  EXPECT_EQ('d', o.conv.Char());
+  EXPECT_EQ('d', FormatConversionCharToChar(o.conv));
   EXPECT_FALSE(o.width.is_from_arg());
   EXPECT_EQ(14, o.width.value());
   EXPECT_FALSE(o.precision.is_from_arg());
@@ -330,7 +328,7 @@ struct SummarizeConsumer {
     if (conv.precision.is_from_arg()) {
       *out += "." + std::to_string(conv.precision.get_from_arg()) + "$*";
     }
-    *out += conv.conv.Char();
+    *out += FormatConversionCharToChar(conv.conv);
     *out += "}";
     return true;
   }