abseil / abseil/abseil-cpp

[Bug]: Defining both AbslFormatConvert and AbslStringify leads to ambiguous function call

Open
#1,966 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
C++
Stars
18.1k
Forks
3.2k
Avg merge
20h 36m
Merged PRs (30d)
1

Description

### Describe the issue

If both AbslStringify and AbslFormatConvert are defined for a custom type (https://abseil.io/docs/cpp/guides/format#user-defined-formats), compilation fails when calling StrFormat.

For example, given

```c++
struct Point {
template
friend void AbslStringify(Sink& sink, const Point& p) {
absl::Format(&sink, "(%d, %d)", p.x, p.y);
}

friend absl::FormatConvertResult
AbslFormatConvert(const Point& p,
const absl::FormatConversionSpec& spec,
absl::FormatSink* s) {
absl::Format(s, "(%d, %d)", p.x, p.y);
return {true};
}

int x;
int y;
};

TEST(CustomStringify, Works) {
EXPECT_EQ(absl::StrFormat("%v", Point{1, 2}), "(1, 2)");
}
```

compilation fails with

```
external/abseil-cpp+/absl/strings/internal/str_format/arg.h:426:31: error: call to 'FormatConvertImpl' is ambiguous
426 | using ConvResult = decltype(str_format_internal::FormatConvertImpl(
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
external/abseil-cpp+/absl/strings/str_format.h:281:26: note: in instantiation of function template specialization 'absl::str_format_internal::ArgumentToConv' requested here
281 | str_format_internal::ArgumentToConv()...>;
| ^
external/abseil-cpp+/absl/strings/str_format.h:362:43: note: in instantiation of template type alias 'FormatSpec' requested here
362 | [[nodiscard]] std::string StrFormat(const FormatSpec& format,
| ^
test.cc:35:13: note: while substituting deduced template arguments into function template 'StrFormat' [with Args = ]
35 | EXPECT_EQ(absl::StrFormat("%v", Point{1, 2}), "(1, 2)");
| ^
external/abseil-cpp+/absl/strings/internal/str_format/arg.h:130:6: note: candidate function [with T = Point]
130 | auto FormatConvertImpl(const T& v, FormatConversionSpecImpl conv,
| ^
external/abseil-cpp+/absl/strings/internal/str_format/arg.h:164:6: note: candidate function [with T = Point]
164 | auto FormatConvertImpl(const T& v, FormatConversionSpecImpl,
| ^
test.cc:35:13: error: no matching function for call to 'StrFormat'
35 | EXPECT_EQ(absl::StrFormat("%v", Point{1, 2}), "(1, 2)");
| ^~~~~~~~~~~~~~~
```

I would expect that this compiles and prefers AbslFormatConvert over AbslStringify (while StrCat and GoogleTest would ignore AbslFormatConvert).

### Steps to reproduce the problem

MODULE.bazel:

```
bazel_dep(name = "rules_cc", version = "0.2.9")
bazel_dep(name = "googletest", version = "1.17.0.bcr.1")
bazel_dep(name = "abseil-cpp", version = "20250814.1")
```

BUILD.bazel:

```
load("@rules_cc//cc:cc_test.bzl", "cc_test")

cc_test(
name = "test",
srcs = ["test.cc"],
deps = [
"@abseil-cpp//absl/strings:str_format",
"@googletest//:gtest",
"@googletest//:gtest_main",
],
)
```

test.cc:

```c++
#include

#include "absl/strings/str_format.h"
#include "gtest/gtest.h"

struct Point {
template
friend void AbslStringify(Sink& sink, const Point& p) {
absl::Format(&sink, "(%d, %d)", p.x, p.y);
}

friend absl::FormatConvertResult
AbslFormatConvert(const Point& p,
const absl::FormatConversionSpec& spec,
absl::FormatSink* s) {
absl::Format(s, "(%d, %d)", p.x, p.y);
return {true};
}

int x;
int y;
};

TEST(CustomStringify, Works) {
EXPECT_EQ(absl::StrFormat("%v", Point{1, 2}), "(1, 2)");
}
```

.bazelrc:

```
build --define=absl=1
```

### What version of Abseil are you using?

20250814.1

### What operating system and version are you using?

macOS Tahoe

### What compiler and version are you using?

Apple clang version 17.0.0 (clang-1700.3.19.1)

### What build system are you using?

bazel 8.4.2

### Additional context

_No response_

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.