1

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.

Mohammad Alaggan
  • 3,749
  • 1
  • 28
  • 20

2 Answers2

3
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.)

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • Is there a way to keep the declaration where it is, but provide the implementation outside the class, in another source file? – Mohammad Alaggan May 18 '16 at 23:23
  • 1
    @M.Alaggan No. These instantiate into a family of non-template functions, and there's no way to spell that outside the class template definition. – T.C. May 18 '16 at 23:27
0

This will work:

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

    template <int P>
    auto abs(B<P>& b) {
        return b.abs();
    }
}

template <int P>
struct A {
    using B = impl::B<P>;
    friend B;
};

int main() {
    A<3>::B inner;
    abs(inner);
}

This solution uses ADL because abs now in the same namespace as B. Moreover, we avoided having P in a non-deduced context (P is not to the left of some :: symbol) by passing the template parameter again to B. Unfortunately, now B is not a member of A, so if you want to give it access to A's member you are going to have to make it a friend. This previous solution is due to this answer. We improve upon it next.

Alternatively, if you really want B to be a member of A, then all you need is a public facade to ensure the correct redirection:

namespace impl {
    template <typename T>
    struct facade : public T {};

    template <typename T>
    auto abs(facade<T>& b) { return b.abs();    }
}

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

    using B = impl::facade<B_ >;
};

int main() {
    A<3>::B inner;
    abs(inner);
}

The advantage here is that this public facade is reusable for several classes, and that it doesn't have to be a friend.

Community
  • 1
  • 1
Mohammad Alaggan
  • 3,749
  • 1
  • 28
  • 20