I'm trying to instantiate an overloaded template function in a convoluted way, and I'm finding that it gets instantiated unless it's in a namespace. The compilation succeeds either way, but it fails to link if the function isn't instantiated.
I can fix the issue by reordering the program so that the overload is defined before the call, but that's actually what I'm trying to avoid.
I'm guessing this is related to ADL.
I've tried to create a minimal reproducable example - if I simplify anything further (eg replace SFyp with a native type) there ceases to be a distinction between namespaced and non-namespaced versions.
SZug<T, A>.zug()
forwards to baz<T, A>()
which forwards to foo<T, A>()
(foo
is overloaded later).
SFyp
is simply a struct used for T
(native types don't produce same behavior).
main()
calls SZug<T, A>.zug()
If foo
(and its overloads) are defined in a namespace, foo
doesn't instantiate. Otherwise, it does.
ns_used.cpp
// Forwarding chain: SZug.zug() -> baz() -> foo().
namespace ns
{
template <typename T1, typename T2>
void foo(T1 v1, T2 v2); // This is overloaded by SFyp
}
template <typename T1, typename T2>
void baz(T1 v1, T2 v2)
{ ns::foo(v1, v2); }
template <typename T1>
struct SZug
{
template <typename T2>
void zug(T2 v2)
{
T1 v1;
baz(v1, v2);
}
};
// SFyp overload of foo().
struct SFyp{};
namespace ns
{
template <typename T2>
void foo(SFyp fyp, T2 v2)
{}
}
// Test.
int main()
{
SZug<SFyp> z;
z.zug(1);
}
g -o ns_used.run ns_used.cpp
fails to link:
clang -o ns_used.run ns_used.cpp
fails to link:
in function `void baz<SFyp, int>(SFyp, int)':
ns_used.cpp:(.text._Z3bazI4SFypiEvT_T0_[_Z3bazI4SFypiEvT_T0_] 0xf): undefined reference to `void ns::foo<SFyp, int>(SFyp, int)'
ns_unused.cpp
// Forwarding chain: SZug.zug() -> baz() -> foo().
//namespace ns
// {
template <typename T1, typename T2>
void foo(T1 v1, T2 v2); // This is overloaded by SFyp
// }
template <typename T1, typename T2>
void baz(T1 v1, T2 v2)
{ /*ns::*/foo(v1, v2); }
template <typename T1>
struct SZug
{
template <typename T2>
void zug(T2 v2)
{
T1 v1;
baz(v1, v2);
}
};
// SFyp overload of foo().
struct SFyp{};
//namespace ns
// {
template <typename T2>
void foo(SFyp fyp, T2 v2)
{}
// }
// Test.
int main()
{
SZug<SFyp> z;
z.zug(1);
}
g -o ns_unused.run ns_unused.cpp
succeeds.
clang -o ns_unused.run ns_unused.cpp
succeeds.
Note: They're identical except for commented-out namespacing.
And, as mentioned above, if I put SFyp
and its foo
overload before baz
, it succeeds with or without namespacing.
What is the reason for this odd behavior? Is it ADL?
Is there any way to fix this so that my overload is considered when baz
is looking for a foo
, while still using namespacing?
CodePudding user response:
TLDR
ADL is for looking up unqualified function names like foo
and not for qualified function names like ns::foo
or ::foo
.
What is the reason for this odd behavior?
The problem is that ADL can't happen for qualified names like ns::foo
or ::foo
. This is the reason if you replace foo
with ::foo
in your second example, then you'll get the exact same error. Demo. And you've provided only a declaration for the primary function template and not a definition and moreover you've provided the foo
overload for SFyp
after defining baz
so that qualified lookup can't find that foo
overload's definition. And as we now know that ADL won't be happening, essentially we get the linker error at the end for the missing definition of the primary function template since the qualified lookup only found a declaration and so was not able to instantiate a definition from that declaration. Meaning that only the declaration template<> void ns::foo<SFyp, int>(SFyp, int)
was instantiated. This is exactly was the linker error says.
On the other hand, for an unqualified name like foo
the ADL can work. This is why your second example works. Basically, in your 2nd example ADL will be happening so that the linker has the definition available and so there will be no error in your 2nd example.
There are 3 ways to solve this. The third way to fix this is simply provide a definition for the primary function template. Demo. The other two are given at the end of the answer.
This can be seen from ADL documentation:
Argument-dependent lookup, also known as ADL, or Koenig lookup [1], is the set of rules for looking up the unqualified function names in function-call expressions, including implicit function calls to overloaded operators.
(emphasis mine)
This means that since ns::foo
and ::foo
are qualified function named, ADL won't happen here. But if you were to use just the unqualified function name foo
then ADL can work(as you noticed in your 2nd example).
The same can be seen from basic.lookup.argdep which says:
When the postfix-expression in a function call is an unqualified-id, other namespaces not considered during the usual unqualified lookup may be searched, and in those namespaces, namespace-scope friend function or function template declarations ([class.friend]) not otherwise visible may be found.
(emphasis mine)
This means that since in your first example the postfix expression ns::foo
is a qualified-id so ADL can't work for that and you get the mentioned error. But in your second example foo
is an unqualified-id so ADL can work here and so your 2nd example works.
Solutions
There are 2 ways of solving this both of which are shown below:
Method 1
Below is the working solution where i've forward declared SFyp
in the global namespace and also declared the function template void foo(SFyp fyp, T2 v2)
in the ns
namespace so that the desired overload is called.
#include <iostream>
#include <type_traits>
#include <vector>
struct SFyp; //I ADDED THIS
namespace ns
{
template <typename T1, typename T2>
void foo(T1 v1, T2 v2);
template <typename T2> //I ADDED THIS
void foo(SFyp fyp, T2 v2);
}
template <typename T1, typename T2>
void baz(T1 v1, T2 v2)
{ ns::foo(v1, v2); }
template <typename T1>
struct SZug
{
template <typename T2>
void zug(T2 v2)
{
T1 v1;
baz(v1, v2);
}
};
// SFyp overload of foo().
struct SFyp{};
namespace ns
{
template <typename T2>
void foo(SFyp fyp, T2 v2)
{std::cout <<"desired overload called";} //I ADDED THIS
}
// Test.
int main()
{
SZug<SFyp> z;
z.zug(1);
}
Output of the modified program is:
desired overload called
Method 2
The second method is to just move the foo
overload before baz
's definition as shown below:
namespace ns
{
template <typename T1, typename T2>
void foo(T1 v1, T2 v2);
}
//-------------------I MOVED THIS HERE-----------------------------------//
struct SFyp{};
namespace ns
{
template <typename T2>
void foo(SFyp fyp, T2 v2)
{std::cout << "desired overload";}
}
//------------------------------------------------------//
template <typename T1, typename T2>
void baz(T1 v1, T2 v2)
{ ns::foo(v1, v2); }
template <typename T1>
struct SZug
{
template <typename T2>
void zug(T2 v2)
{
T1 v1;
baz(v1, v2);
}
};
int main()
{
SZug<SFyp> z;
z.zug(1);
}