Skip to content

Commit 0a7fe7e

Browse files
Abseil Teamcopybara-github
authored andcommitted
Add absl::strings_internal::AbslStringifyStream
PiperOrigin-RevId: 883300728 Change-Id: Ib5c83114fdc4e292ad8fcf96a899878315707353
1 parent 81f4d83 commit 0a7fe7e

5 files changed

Lines changed: 268 additions & 0 deletions

File tree

CMake/AbseilDll.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ set(ABSL_INTERNAL_DLL_FILES
367367
"strings/internal/string_constant.h"
368368
"strings/internal/stringify_sink.cc"
369369
"strings/internal/stringify_sink.h"
370+
"strings/internal/stringify_stream.h"
370371
"strings/internal/utf8.cc"
371372
"strings/internal/utf8.h"
372373
"strings/match.cc"

absl/strings/BUILD.bazel

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,35 @@ cc_test(
424424
],
425425
)
426426

427+
cc_library(
428+
name = "stringify_stream",
429+
hdrs = ["internal/stringify_stream.h"],
430+
copts = ABSL_DEFAULT_COPTS,
431+
linkopts = ABSL_DEFAULT_LINKOPTS,
432+
visibility = [
433+
"//visibility:private",
434+
],
435+
deps = [
436+
":string_view",
437+
"//absl/base:config",
438+
],
439+
)
440+
441+
cc_test(
442+
name = "stringify_stream_test",
443+
srcs = ["internal/stringify_stream_test.cc"],
444+
copts = ABSL_TEST_COPTS,
445+
linkopts = ABSL_DEFAULT_LINKOPTS,
446+
deps = [
447+
":string_view",
448+
":stringify_stream",
449+
"//absl/base:config",
450+
"//absl/strings:str_format",
451+
"@googletest//:gtest",
452+
"@googletest//:gtest_main",
453+
],
454+
)
455+
427456
cc_binary(
428457
name = "charset_benchmark",
429458
testonly = True,

absl/strings/CMakeLists.txt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,32 @@ absl_cc_library(
118118
PUBLIC
119119
)
120120

121+
absl_cc_library(
122+
NAME
123+
stringify_stream
124+
HDRS
125+
"internal/stringify_stream.h"
126+
COPTS
127+
${ABSL_DEFAULT_COPTS}
128+
DEPS
129+
absl::string_view
130+
absl::config
131+
)
132+
133+
absl_cc_test(
134+
NAME
135+
stringify_stream_test
136+
SRCS
137+
"internal/stringify_stream_test.cc"
138+
COPTS
139+
${ABSL_TEST_COPTS}
140+
DEPS
141+
absl::string_view
142+
absl::stringify_stream
143+
absl::str_format
144+
GTest::gmock_main
145+
)
146+
121147
# Internal-only target, do not depend on directly.
122148
absl_cc_library(
123149
NAME
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Copyright 2026 The Abseil Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
// -----------------------------------------------------------------------------
16+
// File: stringify_stream.h
17+
// -----------------------------------------------------------------------------
18+
19+
#ifndef ABSL_STRINGS_INTERNAL_STRINGIFY_STREAM_H_
20+
#define ABSL_STRINGS_INTERNAL_STRINGIFY_STREAM_H_
21+
22+
// StringifyStream is an adaptor for any std::ostream, that provides a
23+
// stream insertion (<<) operator with the following behavior when inserting
24+
// some value of type T:
25+
//
26+
// - If there is a suitable overload of operator<< already defined for T, it
27+
// will be used.
28+
//
29+
// - If there is no operator<< overload, but there is an AbslStringify defined
30+
// for T, it will be used as a fallback.
31+
//
32+
// - Otherwise it is a compilation error.
33+
//
34+
// For reference, AbslStringify typically has the form:
35+
//
36+
// struct Foo {
37+
// template <typename Sink>
38+
// friend void AbslStringify(Sink& sink, const Foo& foo) { ... }
39+
// };
40+
//
41+
// This permits the following usage, for example:
42+
//
43+
// StringifyStream(std::cout) << Foo();
44+
//
45+
46+
#include <cstddef>
47+
#include <ostream>
48+
#include <string>
49+
#include <type_traits>
50+
#include <utility>
51+
52+
#include "absl/base/config.h"
53+
#include "absl/strings/string_view.h"
54+
55+
namespace absl {
56+
ABSL_NAMESPACE_BEGIN
57+
58+
namespace strings_internal {
59+
60+
class StringifyStream {
61+
public:
62+
// Constructor: adapts (but does not take ownership of) some underlying
63+
// std::ostream instance.
64+
explicit StringifyStream(std::ostream& os) : sink_{os} {}
65+
66+
// Stream insertion: delegate to an overload of operator<< if defined for type
67+
// T, otherwise fall back to AbslStringify for T.
68+
template <typename T>
69+
friend StringifyStream& operator<<(StringifyStream& stream, const T& t) {
70+
if constexpr (HasStreamInsertion<T>::value) {
71+
stream.sink_.os << t;
72+
} else {
73+
AbslStringify(stream.sink_, t);
74+
}
75+
return stream;
76+
}
77+
78+
// Rvalue-ref overload, required when the StringifyStream parameter hasn't
79+
// been bound to a variable.
80+
template <typename T>
81+
friend StringifyStream& operator<<(StringifyStream&& stream, const T& t) {
82+
return stream << t;
83+
}
84+
85+
private:
86+
// Abseil "stringify sink" concept (stringify_sink.h)
87+
struct Sink {
88+
std::ostream& os;
89+
void Append(size_t count, char ch) { os << std::string(count, ch); }
90+
void Append(absl::string_view v) { os << v; }
91+
friend void AbslFormatFlush(Sink* sink, absl::string_view v) {
92+
sink->Append(v);
93+
}
94+
} sink_;
95+
96+
// SFINAE helper to identify types with a defined operator<< overload.
97+
template <typename T, typename = void>
98+
struct HasStreamInsertion : std::false_type {};
99+
100+
template <typename T>
101+
struct HasStreamInsertion<T,
102+
std::void_t<decltype(std::declval<std::ostream&>()
103+
<< std::declval<const T&>())>>
104+
: std::true_type {};
105+
};
106+
107+
} // namespace strings_internal
108+
109+
ABSL_NAMESPACE_END
110+
} // namespace absl
111+
112+
#endif // ABSL_STRINGS_INTERNAL_STRINGIFY_STREAM_H_
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Copyright 2026 The Abseil Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "absl/strings/internal/stringify_stream.h"
16+
17+
#include <cstddef>
18+
#include <ostream>
19+
#include <sstream>
20+
21+
#include "gtest/gtest.h"
22+
#include "absl/base/config.h"
23+
#include "absl/strings/str_format.h"
24+
#include "absl/strings/string_view.h"
25+
26+
namespace absl {
27+
ABSL_NAMESPACE_BEGIN
28+
namespace strings_internal {
29+
namespace {
30+
31+
// Exercises the Append(size_t, char) overload
32+
struct AppendNCharsTest {
33+
size_t count;
34+
char ch;
35+
36+
template <typename Sink>
37+
friend void AbslStringify(Sink& sink, const AppendNCharsTest& t) {
38+
sink.Append(t.count, t.ch);
39+
}
40+
};
41+
TEST(StringifyStreamTest, AppendNChars) {
42+
std::ostringstream os;
43+
StringifyStream(os) << AppendNCharsTest{5, 'a'};
44+
EXPECT_EQ(os.str(), "aaaaa");
45+
}
46+
47+
// Exercises the Append(absl::string_view) overload
48+
struct AppendStringViewTest {
49+
absl::string_view v;
50+
51+
template <typename Sink>
52+
friend void AbslStringify(Sink& sink, const AppendStringViewTest& t) {
53+
sink.Append(t.v);
54+
}
55+
};
56+
TEST(StringifyStreamTest, AppendStringView) {
57+
std::ostringstream os;
58+
StringifyStream(os) << AppendStringViewTest{"abc"};
59+
EXPECT_EQ(os.str(), "abc");
60+
}
61+
62+
// Exercises AbslFormatFlush(OStringStreamSink*, absl::string_view v)
63+
struct AbslFormatFlushTest {
64+
absl::string_view a, b, c;
65+
66+
template <typename Sink>
67+
friend void AbslStringify(Sink& sink, const AbslFormatFlushTest& t) {
68+
absl::Format(&sink, "%s, %s, %s", t.a, t.b, t.c);
69+
}
70+
};
71+
TEST(StringifyStreamTest, AbslFormatFlush) {
72+
std::ostringstream os;
73+
StringifyStream(os) << AbslFormatFlushTest{"a", "b", "c"};
74+
EXPECT_EQ(os.str(), "a, b, c");
75+
}
76+
77+
// If overloads of both AbslStringify and operator<< are defined for the type,
78+
// the operator<< overload should take precedence.
79+
struct PreferStreamInsertionOverAbslStringifyTest {
80+
friend std::ostream& operator<<( // NOLINT(clang-diagnostic-unused-function)
81+
std::ostream& os, const PreferStreamInsertionOverAbslStringifyTest&) {
82+
return os << "good";
83+
}
84+
85+
template <typename Sink>
86+
friend void AbslStringify // NOLINT(clang-diagnostic-unused-function)
87+
(Sink& sink, const PreferStreamInsertionOverAbslStringifyTest&) {
88+
sink.Append("bad");
89+
}
90+
};
91+
TEST(StringifyStreamTest, PreferStreamInsertionOverAbslStringify) {
92+
std::ostringstream os;
93+
StringifyStream(os) << PreferStreamInsertionOverAbslStringifyTest{};
94+
EXPECT_EQ(os.str(), "good");
95+
}
96+
97+
} // namespace
98+
} // namespace strings_internal
99+
ABSL_NAMESPACE_END
100+
} // namespace absl

0 commit comments

Comments
 (0)