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.