Home > OS >  absl::FormatSpec results in ambiguous function call under clang
absl::FormatSpec results in ambiguous function call under clang

Time:02-24

Under Clang, the following code gives me an ambiguous function call error:

#include "absl/strings/str_format.h"

struct Foo {};

template<typename... Args>
void func(absl::FormatSpec<Args...> format, const Args&... args) {}

template<typename... Args>
void func(Foo, absl::FormatSpec<Args...> format, const Args&... args) {}

int main()
{    
    func("%s", "Hello");
    func(Foo{}, absl::string_view{"Test"});
}

I'm trying to understand why Clang can not determine that it should call the func that is overloaded to explicitly accept Foo as its first argument, whereas GCC can.

There's a Godbolt link here.

CodePudding user response:

The reason why you see different behaviour on GCC and Clang is that GCC and Clang are seeing different versions of the FormatSpec class, which is defined here

On GCC, the ABSL_INTERNAL_ENABLE_FORMAT_CHECKER feature is off because this feature relies on the compiler-specific enable_if attribute, which is only supported by Clang. Consequently, GCC sees three constructor declarations, none of which can accept Foo. This means only the second func overload is viable.

On Clang, the ABSL_INTERNAL_ENABLE_FORMAT_CHECKER feature is on, and there are 6 constructor declarations, including one of the form

FormatSpecTemplate(...)  // NOLINT
    __attribute__((unavailable("Format string is not constexpr.")));

This constructor accepts Foo but if it is actually called, then there will be a compilation error. But we don't get that far, because Clang thinks that both func overloads are viable and cannot choose one. The first overload involves a user-defined conversion in the first argument; the second overload involves a user-defined conversion in the second argument; neither is better than the other.

I'm not sure why this particular constructor exists, considering the potential to cause issues like the one you're experiencing. It seems to me that it ought to be constrained, maybe like this, so that it can't possibly take Foo as an argument:

template <class T>
FormatSpecTemplate(T&&)
requires std::convertible_to<T, const char*> ||
         std::convertible_to<T, string_view>
__attribute__((unavailable("Format string is not constexpr.")));

But I'm sure there's some reason why they didn't do this.

  •  Tags:  
  • c
  • Related