Add absl::strings_internal::AbslStringifyStream PiperOrigin-RevId: 883300728 Change-Id: Ib5c83114fdc4e292ad8fcf96a899878315707353
diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake index 164b213..1fa6afb 100644 --- a/CMake/AbseilDll.cmake +++ b/CMake/AbseilDll.cmake
@@ -367,6 +367,7 @@ "strings/internal/string_constant.h" "strings/internal/stringify_sink.cc" "strings/internal/stringify_sink.h" + "strings/internal/stringify_stream.h" "strings/internal/utf8.cc" "strings/internal/utf8.h" "strings/match.cc"
diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel index 4e766fd..6762ecd 100644 --- a/absl/strings/BUILD.bazel +++ b/absl/strings/BUILD.bazel
@@ -424,6 +424,35 @@ ], ) +cc_library( + name = "stringify_stream", + hdrs = ["internal/stringify_stream.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//visibility:private", + ], + deps = [ + ":string_view", + "//absl/base:config", + ], +) + +cc_test( + name = "stringify_stream_test", + srcs = ["internal/stringify_stream_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":string_view", + ":stringify_stream", + "//absl/base:config", + "//absl/strings:str_format", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + cc_binary( name = "charset_benchmark", testonly = True,
diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt index 520755b..50c6b11 100644 --- a/absl/strings/CMakeLists.txt +++ b/absl/strings/CMakeLists.txt
@@ -118,6 +118,32 @@ PUBLIC ) +absl_cc_library( + NAME + stringify_stream + HDRS + "internal/stringify_stream.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::string_view + absl::config +) + +absl_cc_test( + NAME + stringify_stream_test + SRCS + "internal/stringify_stream_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::string_view + absl::stringify_stream + absl::str_format + GTest::gmock_main +) + # Internal-only target, do not depend on directly. absl_cc_library( NAME
diff --git a/absl/strings/internal/stringify_stream.h b/absl/strings/internal/stringify_stream.h new file mode 100644 index 0000000..ca0e07a --- /dev/null +++ b/absl/strings/internal/stringify_stream.h
@@ -0,0 +1,112 @@ +// Copyright 2026 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. +// +// ----------------------------------------------------------------------------- +// File: stringify_stream.h +// ----------------------------------------------------------------------------- + +#ifndef ABSL_STRINGS_INTERNAL_STRINGIFY_STREAM_H_ +#define ABSL_STRINGS_INTERNAL_STRINGIFY_STREAM_H_ + +// StringifyStream is an adaptor for any std::ostream, that provides a +// stream insertion (<<) operator with the following behavior when inserting +// some value of type T: +// +// - If there is a suitable overload of operator<< already defined for T, it +// will be used. +// +// - If there is no operator<< overload, but there is an AbslStringify defined +// for T, it will be used as a fallback. +// +// - Otherwise it is a compilation error. +// +// For reference, AbslStringify typically has the form: +// +// struct Foo { +// template <typename Sink> +// friend void AbslStringify(Sink& sink, const Foo& foo) { ... } +// }; +// +// This permits the following usage, for example: +// +// StringifyStream(std::cout) << Foo(); +// + +#include <cstddef> +#include <ostream> +#include <string> +#include <type_traits> +#include <utility> + +#include "absl/base/config.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +namespace strings_internal { + +class StringifyStream { + public: + // Constructor: adapts (but does not take ownership of) some underlying + // std::ostream instance. + explicit StringifyStream(std::ostream& os) : sink_{os} {} + + // Stream insertion: delegate to an overload of operator<< if defined for type + // T, otherwise fall back to AbslStringify for T. + template <typename T> + friend StringifyStream& operator<<(StringifyStream& stream, const T& t) { + if constexpr (HasStreamInsertion<T>::value) { + stream.sink_.os << t; + } else { + AbslStringify(stream.sink_, t); + } + return stream; + } + + // Rvalue-ref overload, required when the StringifyStream parameter hasn't + // been bound to a variable. + template <typename T> + friend StringifyStream& operator<<(StringifyStream&& stream, const T& t) { + return stream << t; + } + + private: + // Abseil "stringify sink" concept (stringify_sink.h) + struct Sink { + std::ostream& os; + void Append(size_t count, char ch) { os << std::string(count, ch); } + void Append(absl::string_view v) { os << v; } + friend void AbslFormatFlush(Sink* sink, absl::string_view v) { + sink->Append(v); + } + } sink_; + + // SFINAE helper to identify types with a defined operator<< overload. + template <typename T, typename = void> + struct HasStreamInsertion : std::false_type {}; + + template <typename T> + struct HasStreamInsertion<T, + std::void_t<decltype(std::declval<std::ostream&>() + << std::declval<const T&>())>> + : std::true_type {}; +}; + +} // namespace strings_internal + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_STRINGIFY_STREAM_H_
diff --git a/absl/strings/internal/stringify_stream_test.cc b/absl/strings/internal/stringify_stream_test.cc new file mode 100644 index 0000000..8e336a1 --- /dev/null +++ b/absl/strings/internal/stringify_stream_test.cc
@@ -0,0 +1,100 @@ +// Copyright 2026 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. + +#include "absl/strings/internal/stringify_stream.h" + +#include <cstddef> +#include <ostream> +#include <sstream> + +#include "gtest/gtest.h" +#include "absl/base/config.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace strings_internal { +namespace { + +// Exercises the Append(size_t, char) overload +struct AppendNCharsTest { + size_t count; + char ch; + + template <typename Sink> + friend void AbslStringify(Sink& sink, const AppendNCharsTest& t) { + sink.Append(t.count, t.ch); + } +}; +TEST(StringifyStreamTest, AppendNChars) { + std::ostringstream os; + StringifyStream(os) << AppendNCharsTest{5, 'a'}; + EXPECT_EQ(os.str(), "aaaaa"); +} + +// Exercises the Append(absl::string_view) overload +struct AppendStringViewTest { + absl::string_view v; + + template <typename Sink> + friend void AbslStringify(Sink& sink, const AppendStringViewTest& t) { + sink.Append(t.v); + } +}; +TEST(StringifyStreamTest, AppendStringView) { + std::ostringstream os; + StringifyStream(os) << AppendStringViewTest{"abc"}; + EXPECT_EQ(os.str(), "abc"); +} + +// Exercises AbslFormatFlush(OStringStreamSink*, absl::string_view v) +struct AbslFormatFlushTest { + absl::string_view a, b, c; + + template <typename Sink> + friend void AbslStringify(Sink& sink, const AbslFormatFlushTest& t) { + absl::Format(&sink, "%s, %s, %s", t.a, t.b, t.c); + } +}; +TEST(StringifyStreamTest, AbslFormatFlush) { + std::ostringstream os; + StringifyStream(os) << AbslFormatFlushTest{"a", "b", "c"}; + EXPECT_EQ(os.str(), "a, b, c"); +} + +// If overloads of both AbslStringify and operator<< are defined for the type, +// the operator<< overload should take precedence. +struct PreferStreamInsertionOverAbslStringifyTest { + friend std::ostream& operator<<( // NOLINT(clang-diagnostic-unused-function) + std::ostream& os, const PreferStreamInsertionOverAbslStringifyTest&) { + return os << "good"; + } + + template <typename Sink> + friend void AbslStringify // NOLINT(clang-diagnostic-unused-function) + (Sink& sink, const PreferStreamInsertionOverAbslStringifyTest&) { + sink.Append("bad"); + } +}; +TEST(StringifyStreamTest, PreferStreamInsertionOverAbslStringify) { + std::ostringstream os; + StringifyStream(os) << PreferStreamInsertionOverAbslStringifyTest{}; + EXPECT_EQ(os.str(), "good"); +} + +} // namespace +} // namespace strings_internal +ABSL_NAMESPACE_END +} // namespace absl