If I write the following code:
namespace math
{
/** Min of two elements */
template <typename T>
T min(const T a, const T b)
{
return (a < b) ? a : b;
}
/** Min of multiple elements */
template <typename T, typename... Ts>
T min(const T first, const Ts... others)
{
T result = first;
auto temp = { (result = math::min(result, others), 0)... };
(void)temp;
return result;
}
}
and then create a new class with its own override for min
:
/** Vec2 */
struct Vec2
{
float x, y;
Vec2(const float _x, const float _y) : x(_x), y(_y) {}
};
namespace math
{
/** Min override for Vec2 */
Vec2 min(const Vec2 a, const Vec2 b)
{
return Vec2(math::min(a.x, b.x), math::min(a.y, b.y));
}
}
and finally, in main
:
extern Vec2 getNewVec2();
int main()
{
Vec2 v1 = getNewVec2();
Vec2 v2 = getNewVec2();
Vec2 v3 = getNewVec2();
Vec2 v_min = math::min(v1, v2, v3);
}
it fails to compile with the error "no match for 'operator<' (operand types are 'const Vec2' and 'const Vec2')". However, if I remove the math
namespace and have all the min
functions in the global namespace, then everything compiles as expected.
It looks like the min
function which takes multiple arguments is looking for a suitable call to math::min
when compiling (the line starting auto temp = ...
) and is unable to find the Vec2
override, so it tries to use the generic template version at the top, which fails to compile as there is no <
operator overload for Vec2
.
If I move the multiple-arguments version of min
below the Vec2
overload for min
, then it compiles correctly again, regardless of whether I use the namespace or not. This isn't really a viable solution though as the code snippets above are intended to be in multiple files, but it does support the theory that the order of the template functions suddenly becomes important when the namespace is introduced.
Is there a way to allow the multi-argument min
function to find the Vec2
override when using the math
namespace, in the same way that it is able to when in the global namespace?
It is not the namespace itself causing the issue, but the way in which you are calling the function is.
If you call min
unqualified, then ADL is performed in addition to usual unqualified name lookup.
The ADL lookup inside a template is performed from the point of instantiation, not from the point of definition. The usual qualified lookup is however performed only from the point of definition of the template.
So, in the two function templates, if you declare the Vec2
overload later, then they can only find the new overload via ADL, not via usual unqualified lookup. Because you correctly put the min
overload for Vec2
in the same namespace scope as the Vec2
class itself, it will always be found by ADL when needed to.
If you however call a function with a qualified name, then no ADL is performed. Consequently it is impossible to find the min
overload for Vec2
if it is declared after the min
template itself.
To keep min
customizable for additional types in the way that you want to, you need to use unqualified calls in the min
templates and leave the min
overload for Vec2
in the same namespace scope as Vec2
. This is exactly how it works for operator overloads as well.
However, choosing the name min
is a bit dangerous. There is already a function called min
in the standard library which will also participate via ADL if you try to use types from std::
with your min
, potentially causing unexpected overload resolution choices.