c++templatesargument-dependent-lookup

Do namespaces affect order of template function instantiations?


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?


Solution

  • 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.