My coworkers showed me following example today:
Run on gcc.godbolt.org
#include <concepts>
#include <iostream>
template <typename T>
void foo(T)
{
std::cout << "1\n";
}
template <typename T>
void bar(T value)
{
foo(value);
}
void foo(std::same_as<int> auto)
{
std::cout << "2\n";
}
Here, bar(42);
and foo(42);
print 1
and 2
respectively, if you only call one of them.
If you call both (in this order), then:
1
1
.foo
.2
2
in release builds and emits a similar linker error in debug builds (could be affected by incremental linking, didn't investigate).What's going on here? The code looks well-formed to me, but maybe I'm wrong?
Cool. Every compiler is wrong.
Within bar
, the call to foo(value)
only has the unconstrained foo<T>
visible in scope. So when we call foo(value)
, the only possible candidates are (1) that one (2) whatever argument-dependent lookup finds. Since T=int
in our example, and int
has no associated namespaces, (2) is an empty set. As a result, when bar(42)
calls foo(42)
, that's the foo
that is the unconstrained template, which should print 1.
On the other hand, within main
, foo(42)
has two different overloads to consider: the constrained one and the unconstrained one. The constrained one is viable, and is more constrained than the unconstrained one, so that's preferred. So from within main()
, foo(42)
should call the constrained foo(same_as<int> auto)
which should print 2.
To summarize:
Clang gets this wrong because it apparently caches the foo<int>
call and that's incorrect - the other foo
overload isn't a specialization, it's an overload, it needs to be considered separately.
gcc gets this right in that the two different foo
calls call two different foo
function templates, but gets this wrong in that it mangles both the same so that we end up with a linker error. This is Itanium ABI #24.
MSVC gets this wrong in that argument-dependent lookup within bar
for foo(value)
finds the later-declared foo
.
More fun is if you change the functions to be constexpr int
instead of void
, which lets you verify this behavior at compile time... as in:
#include <concepts>
#include <iostream>
template <typename T>
constexpr int foo(T)
{
return 1;
}
template <typename T>
constexpr int bar(T value)
{
return foo(value);
}
constexpr int foo(std::same_as<int> auto)
{
return 2;
}
static_assert(bar(42) == 1);
static_assert(foo(42) == 2);
int main()
{
std::cout << bar(42) << '\n';
std::cout << foo(42) << '\n';
}
Then clang compiles (i.e. it does correctly give you that bar(42) == 1
and foo(42) == 2
from that spot) but then prints 2
twice anyway.
While gcc still compiles, just having the same linker error because it mangles both function templates the same.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With