This is a follow-up of this question: `requires` expressions and function namespaces
Trying to design an answer I stumbled on this strange behavior:
#include <cstdio>
// dummy default declaration
template <typename T>
void foo(T) = delete;
template <typename T>
void bar(T t) {
foo(t);
};
// client types
struct S1 {};
// "specializations" for S1
void foo(S1) { std::puts("foo(S1)"); }
int main() {
foo(S1{}); // calls the expected version
bar(S1{}); // try to call the deleted version the wrong version
}
ouput:
foo(S1)
foo(S1)
But if foo
is inside a namespace:
// dummy default declaration
namespace N {
template <typename T>
void foo(T) = delete;
}
template <typename T>
void bar(T t) {
N::foo(t);
};
// client types
struct S1 {};
// "specializations" for S1
namespace N {
void foo(S1) { std::puts("foo(S1)"); }
} // namespace N
int main() {
N::foo(S1{}); // calls the expected version
bar(S1{}); // try to call the deleted version the wrong version
}
The call to bar
does not compile as only the first occurrences of foo
(the deleted templates here) are considered.
The candidate void N::foo(S1)
is not considered.
Yet If, instead of function definitions, I play with template specialization, the behavior is consistent:
// a kind of trait, false by default
template<typename T> struct Trait : public std::false_type
{};
template <typename T>
constexpr bool b_struct = Trait<T>::value;
// client types
struct S1 {};
struct S2 {};
// specializations for S1
template<> struct Trait<S1> : public std::true_type
{};
int main() {
std::cout<<std::boolalpha<<b_struct<S1><<" expecting true\n";
std::cout<<std::boolalpha<<b_struct<S2><<" expecting false\n";
}
and
// a kind of trait, false by default
namespace N {
template <typename T>
struct Trait : public std::false_type {};
} // namespace N
template <typename T>
constexpr bool b_struct = N::Trait<T>::value;
// client types
struct S1 {};
struct S2 {};
// specializations for S1
namespace N {
template <>
struct Trait<S1> : public std::true_type {};
} // namespace N
int main() {
std::cout << std::boolalpha << b_struct<S1> << " expecting true\n";
std::cout << std::boolalpha << b_struct<S2> << " expecting false\n";
}
both output:
true expecting true
false expecting false
I try to understand through https://en.cppreference.com/w/cpp/language/qualified_lookup and https://en.cppreference.com/w/cpp/language/overload_resolution but to no avail.
Is this behavior normal and why?
Functions can be overloaded. Function templates can be specialized.
These are utterly different things.
Function template specializations do not change which function is found during overload resolution. They only change which one is executed.
Overloads are found during overload resolution. One overload is picked.
// "specializations" for S1
void foo(S1) { std::puts("foo(S1)"); }
this is not a specialization; it is an overload.
I generally advise people to avoid using function template specializations, as function overloading can provide the same functionality but not the opposite. Understanding 2 different systems for no practical additional functionality is something I avoid.
In your first case,
template <typename T>
void bar(T t) {
foo(t);
};
we have an unqualified call to foo
within the root namespace. This call is dependent on a template argument T
.
The compiler does two passes of overload resolution. First, it does a non-argument-dependent overload resolution at the point of definition of the template function bar
. This finds the foo() = delete
overload.
Second, at the point of instantiation of bar<T>
, it does an argument dependent lookup. This starts in the namespace of the type T
(and related namespaces), and looks for foo
overloads that take a T
. In this case, the namespace of T
is the root namespace, where it finds a foo
overload (actually 2).
The two overloads are ordered and one is found to be the chosen overload (the standardese text is annoyingly complex here), and that is the one that is called.
In your second case:
template <typename T>
void bar(T t) {
N::foo(t);
};
you did a fully qualified call to N::foo
. No argument dependent lookup is done.
So the only foo
found is the =delete
one visible at the point of definition of bar
.
Had you done this instead:
template <typename T>
void bar(T t) {
using namespace N;
foo(t);
};
it would no longer block argument dependent lookup of foo
. However, as the overloads of foo(S1)
are in namespace N
, and struct S1
aka T
is in the root namespace, searching for argument-dependent overloads of foo
won't find your foo
in question. This will still only find the =delete
d one.
When you invoke N::foo
after the new foo
s are introduced, you indeed find the ones that are introduced, and the "expected" (by you) foo
is called.
The traits case is unrelated, because it is no longer about overload resolution, but instead about template classes and template class specialization. The rules for this are not very related to the rules for overload resolution; that it works differently shouldn't be a surprise.
To make the overload version work properly, it should look roughly like this:
namespace FuncNS {
template <typename T>
void foo(T) = delete;
}
template <typename T>
void bar(T t) {
using FuncNS::foo; // enable ADL
foo(t);
}
// client types
namespace ClientNS {
struct S1 {};
struct S2 {};
}
// foo overloads go in the ClientNS!
namespace ClientNS {
void foo(S1) { std::puts("foo(S1)"); }
}
int main() {
using FuncNS::foo;
foo(ClientNS::S1{});
bar(ClientNS::S1{});
}
Custom overloads go in the namespace of the type you want to customize for.
If someone directly calls FuncNS::foo
, they are asking to ignore any customization points.