Search code examples
c++templatesinner-classesargument-dependent-lookupname-lookup

Implementing non-member generic function for specific nested class of a class template


I have the following class:

template<int P>
struct A {
    struct B {
        auto abs() const { return 1; }
    };
};

Specifically A is supposed to be the finite field of integers modulo P, and B is the class representing elements from this finite field. Then I have a generic implementation of the extended GCD algorithm which is supposed to use the abs function. The problem I want the extended GCD algorithm implementation to work on basic types (int, long, etc), as well as A<P>::B. So I need to implement a non-member abs that will call A<P>::B::abs. I tried a three techniques, two of them don't work and one that works but is not suitable.

Technique 1 (doesn't work): use argument-dependent lookup (ADL). Define the following inside the body of A but outside the body of B:

auto abs(B const &e) { return e.abs(); }

It won't work since ADL looks only inside namespaces not class scopes. UPDATE : ADL doesn't work here because abs is an instance method. However as per T.C.'s answer, if you make it a friend function it works correctly and ADL finds it!

Technique 2 (doesn't work): use constrained function template in global or namespace scope:

template<int P>
auto abs(typename A<P>::B const &e) { return e.abs(); }

This will also not work because P is in a non-deduced context.

Technique 3 (works, but not satisfactory): use unconstrained function template in global or namespace scope:

template<typename T>
auto abs(T const &e) { return e.abs(); }

This works. However, it is unconstrained. I would like to restrict instantiations of this function template only to A<P> (for P unknown beforehand), and other class templates (for polynomial rings and field extensions). The problem is I am not sure how to use SFINAE if P is in a non-deduced context.


Solution

  • template<int P>
    struct A {
        struct B {
            auto abs() const { return 1; }
        };
        friend auto abs(B const &e) { return e.abs(); }
    //  ^^^^^^
    };
    

    Then let ADL work its magic.

    (Defining it inside B is also fine. The choice's up to you.)