10

I have a following nested template class inside another template class:

template<typename T>
struct A
{
    template<typename V>
    struct B {};
};

What would be the signature of a non-member operator== for the nested type B? The following naïve attempt does not work:

template<typename T, typename V>
bool operator==(A<T>::B<V> left, A<T>::B<V> right);

Clang, GCC and MSVC gives various different errors and/or hints what is wrong such as missing template keyword but none of my attempts to resolve it worked out.

Note that this obviously works:

template<typename T>
struct A
{
    template<typename V>
    struct B {};

    template<typename V>
    friend bool operator==(B<V> left, B<V> right)
    {
        return true;
    }
};

However the reason I need the out of line non-member declaration is to document it using qdoc. The qdoc is using clang to parse the sources and it requires me to provide the declaration of the operator== that I have actually implemented in place like just shown.

LIVE DEMO

Resurrection
  • 3,916
  • 2
  • 34
  • 56

3 Answers3

1

The error is not too far off, as you do need the template keyword, but also typename to denote dependent type. A working example would be of the form:

template <typename T, typename V>
bool operator==(typename A<T>::template B<V> left,
                typename A<T>::template B<V> right) {...}

Although I would suggest instead:

template <typename T, typename V>
using operator_type = typename A<T>::template B<V>;

template <typename T, typename V>
bool operator==(operator_type<T, V> left,
                operator_type<T, V> right) {...}

as a means of mitigating some of the esoteric syntax required. It's one of those weird one off things where you would expect typename to be enough to denote that ::B is a dependent name of A, but you still need the template keyword, because the parser gets notoriously confused when dealing with < and >. This answer does a pretty nice job of explaining why:

After name lookup (3.4) finds that a name is a template-name, if this name is followed by a <, the < is always taken as the beginning of a template-argument-list and never as a name followed by the less-than operator.

Now we are back to the same problem as with typename. What if we can't know yet whether the name is a template when parsing the code? We will need to insert template immediately before the template name, as specified by 14.2/4. This looks like:

t::template f<int>(); // call a function template

Community
  • 1
  • 1
jfh
  • 163
  • 13
1

A direct approach, typename A<T>::template B<V> left, does not work as intended because A<T>:: is a nested-name-specifier, which is a non-deduced context. T has to be passed explicitly: operator==<int>(a, b).

To keep == usable as a binary operator, an out-of-line non-member declaration can be implemented using SFINAE:

template <typename T>
struct A {
  template <typename V>
  struct B {
    using ABT = T;
    using ABV = V;
  };
};

template <typename AB>
std::enable_if_t<
    std::is_same_v<AB, typename A<typename AB::ABT>::template B<typename AB::ABV>>,
    bool
> operator==(AB left, AB right) {
  return true;
}

Full example on godbolt: click.

maxplus
  • 502
  • 1
  • 12
-1

You can have an inline friend declaration and an outline definition

template<typename T>
struct A
{
    template<typename V>
    struct B
    {
        friend bool operator==(B left, B right);
    };
};

template <typename T, typename V>
bool operator==(typename A<T>::template B<V> left, typename A<T>::template B<V> right)
{
    return true;
}

However gcc warns

warning: friend declaration bool operator==(A<T>::B<V>, A<T>::B<V>) declares a non-template function [-Wnon-template-friend]

note: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here)

And to fix that warning we would have to forward declare operator==(B left, B right) before the definition of B, which can only be inside A, which would force it to be a friend of A also.

Caleth
  • 52,200
  • 2
  • 44
  • 75
  • It does not work for me either. It does indeed complain as you point out about non-member being declared (Dan Saks on that topic: https://www.youtube.com/watch?v=POa_V15je8Y) but the non-member definition is not actually used. It suffers from the same problem as what was proposed in the OP's comments (now deleted). Forward declaring it as you suggest and adding `<>` to the friend declaration complains about no matching template. See here: https://godbolt.org/z/O5KNu6 – Resurrection Oct 18 '18 at 10:53
  • Worth noting is that when trying out with Clang it points to the same underlying issue we are all seeing in all our attempts: `"Cannot infer template type T"`. – Resurrection Oct 18 '18 at 10:55