#1966·abseil-cpp

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

Author: phstCreated Oct 29, 2025Updated Aug 7, 2026

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

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

  friend absl::FormatConvertResult<absl::FormatConversionCharSet::v>
  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<Point>' requested here
  281 |     str_format_internal::ArgumentToConv<Args>()...>;
      |                          ^
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<Args...>& format,
      |                                           ^
test.cc:35:13: note: while substituting deduced template arguments into function template 'StrFormat' [with Args = <Point>]
   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:

cpp
#include <string>

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

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

  friend absl::FormatConvertResult<absl::FormatConversionCharSet::v>
  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