Export of internal Abseil changes

--
97faa5fdfa4cd5d7a74cd9332cddd8a7c1e67b89 by Abseil Team <absl-team@google.com>:

Internal changes

PiperOrigin-RevId: 295164378

--
74990f100b3f4172c770ef8c76c05c8e99febdde by Xiaoyi Zhang <zhangxy@google.com>:

Release `absl::Cord`.

PiperOrigin-RevId: 295161959

--
6018c57f43c45c31dc1a61c0cd75fa2aa9be8dab by Gennadiy Rozental <rogeeff@google.com>:

Introduce independent notion of FlagStaticTypeID.

This change separates static flag value type identification from the type specific "vtable" with all the operations specific to value type. This change allows us to do the following:
* We can move most of "vtable" implementation from handle header, which will become public soon, into implementation details of Abseil Flag.
* We can combine back marshalling ops and general ops into a single vtable routine. They were split previously to facilitate type identification without requiring marshalling routines to be exposed in header.
* We do not need to store two vtable pointers. We can now store only one. The static type id can be deduced on request.

Overall we are saving 24 bytes per flag according to size_tester run.

PiperOrigin-RevId: 295149687

--
986b78e9ba571aa85154e70bda4580edd45bb7bf by Abseil Team <absl-team@google.com>:

Update internal comments.

PiperOrigin-RevId: 295030681

--
825412b29fd6015027bbc3e5f802706eee0d2837 by Matthew Brown <matthewbr@google.com>:

Change str_format_internal::ConversionChar to an enum (from a struct-wrapped enum).

PiperOrigin-RevId: 294987462

--
f9f88d91809d2cc33fc129df70fa93e7a2c35c69 by Derek Mauro <dmauro@google.com>:

Use more precise wording in the question on live-at-head

PiperOrigin-RevId: 294957679
GitOrigin-RevId: 97faa5fdfa4cd5d7a74cd9332cddd8a7c1e67b89
Change-Id: I081e70d148ffac7296d65e2a2f775f643eaf70bf
This commit is contained in:
Abseil Team 2020-02-14 09:41:25 -08:00 committed by Mark Barolak
parent c44657f556
commit 3c81410510
27 changed files with 5363 additions and 351 deletions

32
FAQ.md
View file

@ -33,8 +33,9 @@ instructions](https://github.com/abseil/abseil-cpp/blob/master/CMake/README.md)
for more information.
For a longer answer to this question and to understand why some other approaches
don't work, see the answer to "What is ABI and why don't you recommend using a
pre-compiled version of Abseil?"
don't work, see the answer to ["What is ABI and why don't you recommend using a
pre-compiled version of
Abseil?"](#what-is-abi-and-why-dont-you-recommend-using-a-pre-compiled-version-of-abseil)
## What is ABI and why don't you recommend using a pre-compiled version of Abseil?
@ -117,7 +118,8 @@ to make it compatible. In practice, the need to use an automated tool is
extremely rare. This means that upgrading from one source release to another
should be a routine practice that can and should be performed often.
We recommend you update to the latest release of Abseil as often as
We recommend you update to the [latest commit in the `master` branch of
Abseil](https://github.com/abseil/abseil-cpp/commits/master) as often as
possible. Not only will you pick up bug fixes more quickly, but if you have good
automated testing, you will catch and be able to fix any [Hyrum's
Law](https://www.hyrumslaw.com/) dependency problems on an incremental basis
@ -130,9 +132,27 @@ feature, updating the
[`http_archive`](https://docs.bazel.build/versions/master/repo/http.html#http_archive)
rule in your
[`WORKSPACE`](https://docs.bazel.build/versions/master/be/workspace.html) for
`com_google_abseil` to point to the latest release is all you need to do. You
can commit the updated `WORKSPACE` file to your source control every time you
update, and if you have good automated testing, you might even consider
`com_google_abseil` to point to the [latest commit in the `master` branch of
Abseil](https://github.com/abseil/abseil-cpp/commits/master) is all you need to
do. For example, on February 11, 2020, the latest commit to the master branch
was `98eb410c93ad059f9bba1bf43f5bb916fc92a5ea`. To update to this commit, you
would add the following snippet to your `WORKSPACE` file:
```
http_archive(
name = "com_google_absl",
urls = ["https://github.com/abseil/abseil-cpp/archive/98eb410c93ad059f9bba1bf43f5bb916fc92a5ea.zip"], # 2020-02-11T18:50:53Z
strip_prefix = "abseil-cpp-98eb410c93ad059f9bba1bf43f5bb916fc92a5ea",
sha256 = "aabf6c57e3834f8dc3873a927f37eaf69975d4b28117fc7427dfb1c661542a87",
)
```
To get the `sha256` of this URL, run `curl -sL --output -
https://github.com/abseil/abseil-cpp/archive/98eb410c93ad059f9bba1bf43f5bb916fc92a5ea.zip
| sha256sum -`.
You can commit the updated `WORKSPACE` file to your source control every time
you update, and if you have good automated testing, you might even consider
automating this.
One thing we don't recommend is using GitHub's `master.zip` files (for example

View file

@ -71,7 +71,7 @@ ABSL_NAMESPACE_BEGIN
// // Now you can use the symbolizer
// }
void InitializeSymbolizer(const char* argv0);
//
// Symbolize()
//
// Symbolizes a program counter (instruction pointer value) `pc` and, on

View file

@ -100,12 +100,10 @@ class Flag {
// constexpr initializable.
#if _MSC_VER <= 1900
constexpr Flag(const char* name, const char* filename,
const flags_internal::FlagMarshallingOpFn marshalling_op,
const flags_internal::HelpGenFunc help_gen,
const flags_internal::FlagDfltGenFunc default_value_gen)
: name_(name),
filename_(filename),
marshalling_op_(marshalling_op),
help_gen_(help_gen),
default_value_gen_(default_value_gen),
inited_(false),
@ -121,7 +119,7 @@ class Flag {
}
impl_ =
new flags_internal::Flag<T>(name_, filename_, marshalling_op_,
new flags_internal::Flag<T>(name_, filename_,
{flags_internal::FlagHelpMsg(help_gen_),
flags_internal::FlagHelpKind::kGenFunc},
default_value_gen_);
@ -161,7 +159,6 @@ class Flag {
// this to be an aggregate type.
const char* name_;
const char* filename_;
const flags_internal::FlagMarshallingOpFn marshalling_op_;
const flags_internal::HelpGenFunc help_gen_;
const flags_internal::FlagDfltGenFunc default_value_gen_;
@ -335,7 +332,6 @@ ABSL_NAMESPACE_END
ABSL_FLAG_IMPL_DECLARE_HELP_WRAPPER(name, help); \
ABSL_CONST_INIT absl::Flag<Type> FLAGS_##name{ \
ABSL_FLAG_IMPL_FLAGNAME(#name), ABSL_FLAG_IMPL_FILENAME(), \
&absl::flags_internal::FlagMarshallingOps<Type>, \
absl::flags_internal::HelpArg<AbslFlagHelpGenFor##name>(0), \
&AbslFlagsInitFlag##name}; \
extern bool FLAGS_no##name; \
@ -349,7 +345,6 @@ ABSL_NAMESPACE_END
ABSL_FLAG_IMPL_DECLARE_HELP_WRAPPER(name, help); \
ABSL_CONST_INIT absl::Flag<Type> FLAGS_##name{ \
ABSL_FLAG_IMPL_FLAGNAME(#name), ABSL_FLAG_IMPL_FILENAME(), \
&absl::flags_internal::FlagMarshallingOps<Type>, \
&AbslFlagHelpGenFor##name::NonConst, &AbslFlagsInitFlag##name}; \
extern bool FLAGS_no##name; \
bool FLAGS_no##name = ABSL_FLAG_IMPL_REGISTRAR(Type, FLAGS_##name)

View file

@ -53,14 +53,13 @@ template <typename T>
bool TestConstructionFor() {
constexpr flags::FlagHelpArg help_arg{flags::FlagHelpMsg("literal help"),
flags::FlagHelpKind::kLiteral};
constexpr flags::Flag<T> f1("f1", "file", &flags::FlagMarshallingOps<T>,
help_arg, &TestMakeDflt<T>);
constexpr flags::Flag<T> f1("f1", "file", help_arg, &TestMakeDflt<T>);
EXPECT_EQ(f1.Name(), "f1");
EXPECT_EQ(f1.Help(), "literal help");
EXPECT_EQ(f1.Filename(), "file");
ABSL_CONST_INIT static flags::Flag<T> f2(
"f2", "file", &flags::FlagMarshallingOps<T>,
"f2", "file",
{flags::FlagHelpMsg(&TestHelpMsg), flags::FlagHelpKind::kGenFunc},
&TestMakeDflt<T>);
flags::FlagRegistrar<T, false>(&f2).OnUpdate(TestCallback);

View file

@ -34,22 +34,23 @@ namespace absl {
ABSL_NAMESPACE_BEGIN
namespace flags_internal {
// Type-specific operations, eg., parsing, copying, etc. are provided
// by function specific to that type with a signature matching FlagOpFn.
enum FlagOp {
kDelete,
kClone,
kCopy,
kCopyConstruct,
kSizeof,
kParse,
kUnparse,
// An alias for flag static type id. Values of type identify the flag value type
// simialarly to typeid(T), but without relying on RTTI being available. In most
// cases this id is enough to uniquely identify the flag's value type. In a few
// cases we'll have to resort to using actual RTTI implementation if it is
// available.
using FlagStaticTypeId = void* (*)();
// Address of this function template is used in current implementation as a flag
// static type id.
template <typename T>
void* FlagStaticTypeIdGen() {
#if defined(ABSL_FLAGS_INTERNAL_HAS_RTTI)
kRuntimeTypeId
return const_cast<std::type_info*>(&typeid(T));
#else
return nullptr;
#endif
};
using FlagOpFn = void* (*)(FlagOp, const void*, void*);
using FlagMarshallingOpFn = void* (*)(FlagOp, const void*, void*, void*);
}
// Options that control SetCommandLineOptionWithMode.
enum FlagSettingMode {
@ -72,97 +73,6 @@ enum ValueSource {
kProgrammaticChange,
};
// The per-type function
template <typename T>
void* FlagOps(FlagOp op, const void* v1, void* v2) {
switch (op) {
case kDelete:
delete static_cast<const T*>(v1);
return nullptr;
case kClone:
return new T(*static_cast<const T*>(v1));
case kCopy:
*static_cast<T*>(v2) = *static_cast<const T*>(v1);
return nullptr;
case kCopyConstruct:
new (v2) T(*static_cast<const T*>(v1));
return nullptr;
case kSizeof:
return reinterpret_cast<void*>(sizeof(T));
#if defined(ABSL_FLAGS_INTERNAL_HAS_RTTI)
case kRuntimeTypeId:
return const_cast<std::type_info*>(&typeid(T));
break;
#endif
default:
return nullptr;
}
}
template <typename T>
void* FlagMarshallingOps(FlagOp op, const void* v1, void* v2, void* v3) {
switch (op) {
case kParse: {
// initialize the temporary instance of type T based on current value in
// destination (which is going to be flag's default value).
T temp(*static_cast<T*>(v2));
if (!absl::ParseFlag<T>(*static_cast<const absl::string_view*>(v1), &temp,
static_cast<std::string*>(v3))) {
return nullptr;
}
*static_cast<T*>(v2) = std::move(temp);
return v2;
}
case kUnparse:
*static_cast<std::string*>(v2) =
absl::UnparseFlag<T>(*static_cast<const T*>(v1));
return nullptr;
default:
return nullptr;
}
}
// Functions that invoke flag-type-specific operations.
inline void Delete(FlagOpFn op, const void* obj) {
op(flags_internal::kDelete, obj, nullptr);
}
inline void* Clone(FlagOpFn op, const void* obj) {
return op(flags_internal::kClone, obj, nullptr);
}
inline void Copy(FlagOpFn op, const void* src, void* dst) {
op(flags_internal::kCopy, src, dst);
}
inline void CopyConstruct(FlagOpFn op, const void* src, void* dst) {
op(flags_internal::kCopyConstruct, src, dst);
}
inline bool Parse(FlagMarshallingOpFn op, absl::string_view text, void* dst,
std::string* error) {
return op(flags_internal::kParse, &text, dst, error) != nullptr;
}
inline std::string Unparse(FlagMarshallingOpFn op, const void* val) {
std::string result;
op(flags_internal::kUnparse, val, &result, nullptr);
return result;
}
inline size_t Sizeof(FlagOpFn op) {
// This sequence of casts reverses the sequence from base::internal::FlagOps()
return static_cast<size_t>(reinterpret_cast<intptr_t>(
op(flags_internal::kSizeof, nullptr, nullptr)));
}
#if defined(ABSL_FLAGS_INTERNAL_HAS_RTTI)
inline const std::type_info& RuntimeTypeId(FlagOpFn op) {
return *static_cast<const std::type_info*>(
op(flags_internal::kRuntimeTypeId, nullptr, nullptr));
}
#endif
// Handle to FlagState objects. Specific flag state objects will restore state
// of a flag produced this flag state from method CommandLineFlag::SaveState().
class FlagStateInterface {
@ -187,7 +97,7 @@ class CommandLineFlag {
// Return true iff flag has type T.
template <typename T>
inline bool IsOfType() const {
return TypeId() == &flags_internal::FlagOps<T>;
return TypeId() == &flags_internal::FlagStaticTypeIdGen<T>;
}
// Attempts to retrieve the flag value. Returns value on success,
@ -240,7 +150,7 @@ class CommandLineFlag {
// Returns true iff this is a handle to an Abseil Flag.
virtual bool IsAbseilFlag() const { return true; }
// Returns id of the flag's value type.
virtual flags_internal::FlagOpFn TypeId() const = 0;
virtual FlagStaticTypeId TypeId() const = 0;
virtual bool IsModified() const = 0;
virtual bool IsSpecifiedOnCommandLine() const = 0;
virtual std::string DefaultValue() const = 0;

View file

@ -47,23 +47,15 @@ const char kStrippedFlagHelp[] = "\001\002\003\004 (unknown) \004\003\002\001";
namespace {
// Currently we only validate flag values for user-defined flag types.
bool ShouldValidateFlagValue(FlagOpFn flag_type_id) {
bool ShouldValidateFlagValue(FlagStaticTypeId flag_type_id) {
#define DONT_VALIDATE(T) \
if (flag_type_id == &flags_internal::FlagOps<T>) return false;
if (flag_type_id == &FlagStaticTypeIdGen<T>) return false;
ABSL_FLAGS_INTERNAL_BUILTIN_TYPES(DONT_VALIDATE)
#undef DONT_VALIDATE
return true;
}
#if defined(ABSL_FLAGS_INTERNAL_HAS_RTTI)
bool MatchRuntimeTypeId(FlagOpFn lhs_type_id, FlagOpFn rhs_type_id) {
return RuntimeTypeId(lhs_type_id) == RuntimeTypeId(rhs_type_id);
}
#else
bool MatchRuntimeTypeId(FlagOpFn, FlagOpFn) { return true; }
#endif
// RAII helper used to temporarily unlock and relock `absl::Mutex`.
// This is used when we need to ensure that locks are released while
// invoking user supplied callbacks and then reacquired, since callbacks may
@ -101,22 +93,35 @@ absl::Mutex* FlagImpl::DataGuard() const {
return reinterpret_cast<absl::Mutex*>(&data_guard_);
}
void FlagImpl::AssertValidType(const flags_internal::FlagOpFn op) const {
// `op` is the unmarshaling operation corresponding to the declaration
// visibile at the call site. `op_` is the Flag's defined unmarshalling
// operation. They must match for this operation to be well-defined.
if (ABSL_PREDICT_FALSE(op != op_) && !MatchRuntimeTypeId(op, op_)) {
ABSL_INTERNAL_LOG(
FATAL,
absl::StrCat("Flag '", Name(),
"' is defined as one type and declared as another"));
}
void FlagImpl::AssertValidType(FlagStaticTypeId type_id) const {
FlagStaticTypeId this_type_id = flags_internal::StaticTypeId(op_);
// `type_id` is the type id corresponding to the declaration visibile at the
// call site. `this_type_id` is the type id corresponding to the type stored
// during flag definition. They must match for this operation to be
// well-defined.
if (ABSL_PREDICT_TRUE(type_id == this_type_id)) return;
void* lhs_runtime_type_id = type_id();
void* rhs_runtime_type_id = this_type_id();
if (lhs_runtime_type_id == rhs_runtime_type_id) return;
#if defined(ABSL_FLAGS_INTERNAL_HAS_RTTI)
if (*reinterpret_cast<std::type_info*>(lhs_runtime_type_id) ==
*reinterpret_cast<std::type_info*>(rhs_runtime_type_id))
return;
#endif
ABSL_INTERNAL_LOG(
FATAL, absl::StrCat("Flag '", Name(),
"' is defined as one type and declared as another"));
}
std::unique_ptr<void, DynValueDeleter> FlagImpl::MakeInitValue() const {
void* res = nullptr;
if (DefaultKind() == FlagDefaultKind::kDynamicValue) {
res = Clone(op_, default_src_.dynamic_value);
res = flags_internal::Clone(op_, default_src_.dynamic_value);
} else {
res = (*default_src_.gen_func)();
}
@ -148,13 +153,13 @@ std::string FlagImpl::DefaultValue() const {
absl::MutexLock l(DataGuard());
auto obj = MakeInitValue();
return Unparse(marshalling_op_, obj.get());
return flags_internal::Unparse(op_, obj.get());
}
std::string FlagImpl::CurrentValue() const {
absl::MutexLock l(DataGuard());
return Unparse(marshalling_op_, value_.dynamic);
return flags_internal::Unparse(op_, value_.dynamic);
}
void FlagImpl::SetCallback(const FlagCallbackFunc mutation_callback) {
@ -220,7 +225,7 @@ bool FlagImpl::TryParse(void** dst, absl::string_view value,
auto tentative_value = MakeInitValue();
std::string parse_err;
if (!Parse(marshalling_op_, value, tentative_value.get(), &parse_err)) {
if (!flags_internal::Parse(op_, value, tentative_value.get(), &parse_err)) {
absl::string_view err_sep = parse_err.empty() ? "" : "; ";
*err = absl::StrCat("Illegal value '", value, "' specified for flag '",
Name(), "'", err_sep, parse_err);
@ -237,11 +242,11 @@ bool FlagImpl::TryParse(void** dst, absl::string_view value,
void FlagImpl::Read(void* dst) const {
absl::ReaderMutexLock l(DataGuard());
CopyConstruct(op_, value_.dynamic, dst);
flags_internal::CopyConstruct(op_, value_.dynamic, dst);
}
void FlagImpl::StoreAtomic() {
size_t data_size = Sizeof(op_);
size_t data_size = flags_internal::Sizeof(op_);
if (data_size <= sizeof(int64_t)) {
int64_t t = 0;
@ -260,20 +265,20 @@ void FlagImpl::StoreAtomic() {
void FlagImpl::Write(const void* src) {
absl::MutexLock l(DataGuard());
if (ShouldValidateFlagValue(op_)) {
void* obj = Clone(op_, src);
if (ShouldValidateFlagValue(flags_internal::StaticTypeId(op_))) {
void* obj = flags_internal::Clone(op_, src);
std::string ignored_error;
std::string src_as_str = Unparse(marshalling_op_, src);
if (!Parse(marshalling_op_, src_as_str, obj, &ignored_error)) {
std::string src_as_str = flags_internal::Unparse(op_, src);
if (!flags_internal::Parse(op_, src_as_str, obj, &ignored_error)) {
ABSL_INTERNAL_LOG(ERROR, absl::StrCat("Attempt to set flag '", Name(),
"' to invalid value ", src_as_str));
}
Delete(op_, obj);
flags_internal::Delete(op_, obj);
}
modified_ = true;
counter_++;
Copy(op_, src, value_.dynamic);
flags_internal::Copy(op_, src, value_.dynamic);
StoreAtomic();
InvokeCallback();
@ -341,7 +346,7 @@ bool FlagImpl::SetFromString(absl::string_view value, FlagSettingMode set_mode,
if (!modified_) {
// Need to set both default value *and* current, in this case
Copy(op_, default_src_.dynamic_value, value_.dynamic);
flags_internal::Copy(op_, default_src_.dynamic_value, value_.dynamic);
StoreAtomic();
InvokeCallback();
}
@ -359,7 +364,7 @@ void FlagImpl::CheckDefaultValueParsingRoundtrip() const {
auto dst = MakeInitValue();
std::string error;
if (!flags_internal::Parse(marshalling_op_, v, dst.get(), &error)) {
if (!flags_internal::Parse(op_, v, dst.get(), &error)) {
ABSL_INTERNAL_LOG(
FATAL,
absl::StrCat("Flag ", Name(), " (from ", Filename(),
@ -376,8 +381,7 @@ bool FlagImpl::ValidateInputValue(absl::string_view value) const {
auto obj = MakeInitValue();
std::string ignored_error;
return flags_internal::Parse(marshalling_op_, value, obj.get(),
&ignored_error);
return flags_internal::Parse(op_, value, obj.get(), &ignored_error);
}
} // namespace flags_internal

View file

@ -42,6 +42,94 @@ namespace flags_internal {
template <typename T>
class Flag;
///////////////////////////////////////////////////////////////////////////////
// Type-specific operations, eg., parsing, copying, etc. are provided
// by function specific to that type with a signature matching FlagOpFn.
enum FlagOp {
kDelete,
kClone,
kCopy,
kCopyConstruct,
kSizeof,
kStaticTypeId,
kParse,
kUnparse,
};
using FlagOpFn = void* (*)(FlagOp, const void*, void*, void*);
// The per-type function
template <typename T>
void* FlagOps(FlagOp op, const void* v1, void* v2, void* v3) {
switch (op) {
case flags_internal::kDelete:
delete static_cast<const T*>(v1);
return nullptr;
case flags_internal::kClone:
return new T(*static_cast<const T*>(v1));
case flags_internal::kCopy:
*static_cast<T*>(v2) = *static_cast<const T*>(v1);
return nullptr;
case flags_internal::kCopyConstruct:
new (v2) T(*static_cast<const T*>(v1));
return nullptr;
case flags_internal::kSizeof:
return reinterpret_cast<void*>(sizeof(T));
case flags_internal::kStaticTypeId:
return reinterpret_cast<void*>(&FlagStaticTypeIdGen<T>);
case flags_internal::kParse: {
// Initialize the temporary instance of type T based on current value in
// destination (which is going to be flag's default value).
T temp(*static_cast<T*>(v2));
if (!absl::ParseFlag<T>(*static_cast<const absl::string_view*>(v1), &temp,
static_cast<std::string*>(v3))) {
return nullptr;
}
*static_cast<T*>(v2) = std::move(temp);
return v2;
}
case flags_internal::kUnparse:
*static_cast<std::string*>(v2) =
absl::UnparseFlag<T>(*static_cast<const T*>(v1));
return nullptr;
default:
return nullptr;
}
}
// Functions that invoke flag-type-specific operations.
inline void Delete(FlagOpFn op, const void* obj) {
op(flags_internal::kDelete, obj, nullptr, nullptr);
}
inline void* Clone(FlagOpFn op, const void* obj) {
return op(flags_internal::kClone, obj, nullptr, nullptr);
}
inline void Copy(FlagOpFn op, const void* src, void* dst) {
op(flags_internal::kCopy, src, dst, nullptr);
}
inline void CopyConstruct(FlagOpFn op, const void* src, void* dst) {
op(flags_internal::kCopyConstruct, src, dst, nullptr);
}
inline bool Parse(FlagOpFn op, absl::string_view text, void* dst,
std::string* error) {
return op(flags_internal::kParse, &text, dst, error) != nullptr;
}
inline std::string Unparse(FlagOpFn op, const void* val) {
std::string result;
op(flags_internal::kUnparse, val, &result, nullptr);
return result;
}
inline size_t Sizeof(FlagOpFn op) {
// This sequence of casts reverses the sequence from
// `flags_internal::FlagOps()`
return static_cast<size_t>(reinterpret_cast<intptr_t>(
op(flags_internal::kSizeof, nullptr, nullptr, nullptr)));
}
inline FlagStaticTypeId StaticTypeId(FlagOpFn op) {
return reinterpret_cast<FlagStaticTypeId>(
op(flags_internal::kStaticTypeId, nullptr, nullptr, nullptr));
}
///////////////////////////////////////////////////////////////////////////////
// Persistent state of the flag data.
@ -273,12 +361,10 @@ struct DynValueDeleter {
class FlagImpl {
public:
constexpr FlagImpl(const char* name, const char* filename, FlagOpFn op,
FlagMarshallingOpFn marshalling_op, FlagHelpArg help,
FlagDfltGenFunc default_value_gen)
FlagHelpArg help, FlagDfltGenFunc default_value_gen)
: name_(name),
filename_(filename),
op_(op),
marshalling_op_(marshalling_op),
help_(help.source),
help_source_kind_(static_cast<uint8_t>(help.kind)),
def_kind_(static_cast<uint8_t>(FlagDefaultKind::kGenFunc)),
@ -306,7 +392,7 @@ class FlagImpl {
template <typename T, typename std::enable_if<
!IsAtomicFlagTypeTrait<T>::value, int>::type = 0>
void Get(T* dst) const {
AssertValidType(&flags_internal::FlagOps<T>);
AssertValidType(&flags_internal::FlagStaticTypeIdGen<T>);
Read(dst);
}
// Overload for `GetFlag()` for types that support lock-free reads.
@ -317,7 +403,7 @@ class FlagImpl {
// slowing down flag value access due to type validation. That's why
// this validation is hidden behind !NDEBUG
#ifndef NDEBUG
AssertValidType(&flags_internal::FlagOps<T>);
AssertValidType(&flags_internal::FlagStaticTypeIdGen<T>);
#endif
using U = flags_internal::BestAtomicType<T>;
typename U::type r = value_.atomics.template load<T>();
@ -329,7 +415,7 @@ class FlagImpl {
}
template <typename T>
void Set(const T& src) {
AssertValidType(&flags_internal::FlagOps<T>);
AssertValidType(&flags_internal::FlagStaticTypeIdGen<T>);
Write(&src);
}
@ -388,7 +474,7 @@ class FlagImpl {
// int. To do that we pass the "assumed" type id (which is deduced from type
// int) as an argument `op`, which is in turn is validated against the type id
// stored in flag object by flag definition statement.
void AssertValidType(const flags_internal::FlagOpFn op) const;
void AssertValidType(FlagStaticTypeId type_id) const;
// Immutable flag's state.
@ -396,10 +482,8 @@ class FlagImpl {
const char* const name_;
// The file name where ABSL_FLAG resides.
const char* const filename_;
// Type-specific handler.
// Type-specific operations "vtable".
const FlagOpFn op_;
// Marshalling ops handler.
const FlagMarshallingOpFn marshalling_op_;
// Help message literal or function to generate it.
const FlagHelpMsg help_;
// Indicates if help message was supplied as literal or generator func.
@ -456,12 +540,9 @@ class FlagImpl {
template <typename T>
class Flag final : public flags_internal::CommandLineFlag {
public:
constexpr Flag(const char* name, const char* filename,
const FlagMarshallingOpFn marshalling_op,
const FlagHelpArg help,
constexpr Flag(const char* name, const char* filename, const FlagHelpArg help,
const FlagDfltGenFunc default_value_gen)
: impl_(name, filename, &FlagOps<T>, marshalling_op, help,
default_value_gen) {}
: impl_(name, filename, &FlagOps<T>, help, default_value_gen) {}
T Get() const {
// See implementation notes in CommandLineFlag::Get().
@ -520,7 +601,7 @@ class Flag final : public flags_internal::CommandLineFlag {
friend class FlagState<T>;
void Read(void* dst) const override { impl_.Read(dst); }
FlagOpFn TypeId() const override { return &FlagOps<T>; }
FlagStaticTypeId TypeId() const override { return &FlagStaticTypeIdGen<T>; }
// Flag's data
FlagImpl impl_;

View file

@ -284,14 +284,14 @@ namespace {
class RetiredFlagObj final : public flags_internal::CommandLineFlag {
public:
constexpr RetiredFlagObj(const char* name, FlagOpFn ops)
: name_(name), op_(ops) {}
constexpr RetiredFlagObj(const char* name, FlagStaticTypeId type_id)
: name_(name), type_id_(type_id) {}
private:
absl::string_view Name() const override { return name_; }
std::string Filename() const override { return "RETIRED"; }
absl::string_view Typename() const override { return ""; }
flags_internal::FlagOpFn TypeId() const override { return op_; }
FlagStaticTypeId TypeId() const override { return type_id_; }
std::string Help() const override { return ""; }
bool IsRetired() const override { return true; }
bool IsModified() const override { return false; }
@ -317,7 +317,7 @@ class RetiredFlagObj final : public flags_internal::CommandLineFlag {
// Data members
const char* const name_;
const FlagOpFn op_;
const FlagStaticTypeId type_id_;
};
void DestroyRetiredFlag(flags_internal::CommandLineFlag* flag) {
@ -327,8 +327,8 @@ void DestroyRetiredFlag(flags_internal::CommandLineFlag* flag) {
} // namespace
bool Retire(const char* name, FlagOpFn ops) {
auto* flag = new flags_internal::RetiredFlagObj(name, ops);
bool Retire(const char* name, FlagStaticTypeId type_id) {
auto* flag = new flags_internal::RetiredFlagObj(name, type_id);
FlagRegistry::GlobalRegistry()->RegisterFlag(flag);
return true;
}

View file

@ -79,12 +79,12 @@ bool RegisterCommandLineFlag(CommandLineFlag*);
//
// Retire flag with name "name" and type indicated by ops.
bool Retire(const char* name, FlagOpFn ops);
bool Retire(const char* name, FlagStaticTypeId type_id);
// Registered a retired flag with name 'flag_name' and type 'T'.
template <typename T>
inline bool RetiredFlag(const char* flag_name) {
return flags_internal::Retire(flag_name, flags_internal::FlagOps<T>);
return flags_internal::Retire(flag_name, &FlagStaticTypeIdGen<T>);
}
// If the flag is retired, returns true and indicates in |*type_is_bool|

View file

@ -126,6 +126,7 @@ cc_test(
copts = ABSL_TEST_COPTS,
visibility = ["//visibility:private"],
deps = [
":cord",
":strings",
"//absl/base:core_headers",
"//absl/container:fixed_array",
@ -250,6 +251,74 @@ cc_test(
],
)
cc_library(
name = "cord_internal",
hdrs = ["internal/cord_internal.h"],
copts = ABSL_DEFAULT_COPTS,
visibility = ["//visibility:private"],
deps = [
":strings",
"//absl/meta:type_traits",
],
)
cc_library(
name = "cord",
srcs = [
"cord.cc",
],
hdrs = [
"cord.h",
],
copts = ABSL_DEFAULT_COPTS,
deps = [
":cord_internal",
":internal",
":str_format",
":strings",
"//absl/base",
"//absl/base:base_internal",
"//absl/base:core_headers",
"//absl/base:endian",
"//absl/base:raw_logging_internal",
"//absl/container:fixed_array",
"//absl/container:inlined_vector",
"//absl/functional:function_ref",
"//absl/meta:type_traits",
],
)
cc_library(
name = "cord_test_helpers",
testonly = 1,
hdrs = [
"cord_test_helpers.h",
],
copts = ABSL_DEFAULT_COPTS,
deps = [
":cord",
],
)
cc_test(
name = "cord_test",
size = "medium",
srcs = ["cord_test.cc"],
copts = ABSL_TEST_COPTS,
visibility = ["//visibility:private"],
deps = [
":cord",
":cord_test_helpers",
":strings",
"//absl/base",
"//absl/base:config",
"//absl/base:endian",
"//absl/base:raw_logging_internal",
"//absl/container:fixed_array",
"@com_google_googletest//:gtest_main",
],
)
cc_test(
name = "substitute_test",
size = "small",

View file

@ -526,3 +526,57 @@ absl_cc_test(
absl::str_format
gmock_main
)
absl_cc_library(
NAME
cord
HDRS
"cord.h"
SRCS
"cord.cc"
"internal/cord_internal.h"
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
absl::strings_internal
absl::base
absl::base_internal
absl::core_headers
absl::endian
absl::fixed_array
absl::function_ref
absl::inlined_vector
absl::raw_logging_internal
absl::type_traits
PUBLIC
)
absl_cc_library(
NAME
cord_test_helpers
HDRS
"cord_test_helpers.h"
COPTS
${ABSL_TEST_COPTS}
DEPS
absl::cord
TESTONLY
)
absl_cc_test(
NAME
cord_test
SRCS
"cord_test.cc"
COPTS
${ABSL_TEST_COPTS}
DEPS
absl::cord
absl::strings
absl::base
absl::config
absl::endian
absl::raw_logging_internal
absl::fixed_array
gmock_main
)

2019
absl/strings/cord.cc Normal file

File diff suppressed because it is too large Load diff

1121
absl/strings/cord.h Normal file

File diff suppressed because it is too large Load diff

1526
absl/strings/cord_test.cc Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,60 @@
//
// Copyright 2018 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_CORD_TEST_HELPERS_H_
#define ABSL_STRINGS_CORD_TEST_HELPERS_H_
#include "absl/strings/cord.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
// Creates a multi-segment Cord from an iterable container of strings. The
// resulting Cord is guaranteed to have one segment for every string in the
// container. This allows code to be unit tested with multi-segment Cord
// inputs.
//
// Example:
//
// absl::Cord c = absl::MakeFragmentedCord({"A ", "fragmented ", "Cord"});
// EXPECT_FALSE(c.GetFlat(&unused));
//
// The mechanism by which this Cord is created is an implementation detail. Any
// implementation that produces a multi-segment Cord may produce a flat Cord in
// the future as new optimizations are added to the Cord class.
// MakeFragmentedCord will, however, always be updated to return a multi-segment
// Cord.
template <typename Container>
Cord MakeFragmentedCord(const Container& c) {
Cord result;
for (const auto& s : c) {
auto* external = new std::string(s);
Cord tmp = absl::MakeCordFromExternal(
*external, [external](absl::string_view) { delete external; });
tmp.Prepend(result);
result = tmp;
}
return result;
}
inline Cord MakeFragmentedCord(std::initializer_list<absl::string_view> list) {
return MakeFragmentedCord<std::initializer_list<absl::string_view>>(list);
}
ABSL_NAMESPACE_END
} // namespace absl
#endif // ABSL_STRINGS_CORD_TEST_HELPERS_H_

View file

@ -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_

View file

@ -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};

View file

@ -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>());
}

View file

@ -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);

View file

@ -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;

View file

@ -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);
}
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, )
#undef X_VAL
default:
break;
}
return ConversionChar(out_id);
}
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;
}
}
bool upper() const {
switch (id()) {
case X: case F: case E: case G: case A: return true;
default: return false;
}
}
bool is_signed() const {
switch (id()) {
case d: case 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;
}
}
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;
}
}
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;
}
private:
struct Spec {
Id value;
char name;
};
static const Spec kSpecs[];
explicit ConversionChar(Id id) : id_(id) {}
Id id_;
kNone,
none = kNone
};
// clang-format on
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
}
return FormatConversionChar::kNone;
}
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;
}
}
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;
}
}
inline bool FormatConversionCharIsSigned(FormatConversionChar c) {
switch (c) {
case FormatConversionChar::d:
case FormatConversionChar::i:
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;
}
}
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;
}
}
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';
}
// 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

View file

@ -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:

View file

@ -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;
}

View file

@ -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());

View file

@ -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;
}

View file

@ -450,7 +450,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;
}

View file

@ -1045,7 +1045,7 @@ TEST(Duration, Multiplication) {
EXPECT_EQ(absl::Seconds(666666666) + absl::Nanoseconds(666666667) +
absl::Nanoseconds(1) / 2,
sigfigs / 3);
sigfigs = absl::Seconds(7000000000LL);
sigfigs = absl::Seconds(int64_t{7000000000});
EXPECT_EQ(absl::Seconds(2333333333) + absl::Nanoseconds(333333333) +
absl::Nanoseconds(1) / 4,
sigfigs / 3);