Skip to content

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

@phst

Description

@phst

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

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:

#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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions