2

I am trying to implement an overload for operator!= that compares two objects of different types (InnerA and InnerB). Both of the types are defined as nested classes within a template class (Outer). The overload needs to be a friend of both classes as it accesses private fields from each.

template<typename Type> class Outer
{
public:
    class InnerA;
    class InnerB;
};


template<typename Type> bool operator!=(const typename Outer<Type>::InnerA& lhs, const typename Outer<Type>::InnerB& rhs);


template<typename Type> class Outer<Type>::InnerA
{
    const int val = 0;
    friend bool operator!=<>(const InnerA& lhs, const typename Outer<Type>::InnerB& rhs);
};


template<typename Type> class Outer<Type>::InnerB
{
    const int val = 1;
    friend bool operator!=<>(const typename Outer<Type>::InnerA& lhs, const InnerB& rhs);
};


template<typename Type> bool operator!=(const typename Outer<Type>::InnerA& lhs, const typename Outer<Type>::InnerB& rhs)
{
    return lhs.val != rhs.val;
}


int main()
{
    bool b = Outer<int>::InnerA() != Outer<int>::InnerB();
}

The above code fails to compile, emitting:

 In instantiation of 'class Outer<int>::InnerA':
34:33: required from here 
15:17: error: template-id 'operator!=<>' for 'bool operator!=(const Outer<int>::InnerA&, const Outer<int>::InnerB&)' does not match any template declaration 
 In instantiation of 'class Outer<int>::InnerB': 
34:57: required from here 
22:17: error: template-id 'operator!=<>' for 'bool operator!=(const Outer<int>::InnerA&, const Outer<int>::InnerB&)' does not match any template declaration 
 In function 'int main()': 
34:35: error: no match for 'operator!=' (operand types are 'Outer<int>::InnerA' and 'Outer<int>::InnerB') 
34:35: note: candidate is: 
26:30: note: template<class Type> bool operator!=(const typename Outer<Type>::InnerA&, const typename Outer<Type>::InnerB&) 
26:30: note: template argument deduction/substitution failed: 
34:57: note: couldn't deduce template parameter 'Type'

While there might be better ways to achieve a similar result, I'm curious as to what precisely is wrong with my code. Thanks!

Alex Martin
  • 31
  • 1
  • 6

3 Answers3

1
template<typename Type> class Outer<Type>::InnerA
{
   friend bool operator!=(const InnerA& lhs, const typename Outer<Type>::InnerB& rhs) { return true; }
};
template<typename Type> class Outer<Type>::InnerB
{
  friend bool operator!=(const typename Outer<Type>::InnerA& lhs, const InnerB& rhs) { return true; }
};

these are non-template friends. They also conflict. So implement one of them, omit the other.

They will be found via ADL.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • I don't think this solution will work for my purpose. The `operator!=` overload needs to be a friend of both `InnerA` and `InnerB` to access private fields from each. I'll update my question to make that clear. – Alex Martin May 17 '18 at 04:36
  • @alex Sure, that is an add-on to the question. To solve that, forward to another non-operator. An important part of operators is deducing which one to call; non-template friends are easily found via ADL. Non-deduced context templates, meanwhile, can only be called by passing the types explicitly, which is not what you want in an operator. – Yakk - Adam Nevraumont May 17 '18 at 11:25
  • I suppose this boils down to my lacking an adequate understanding of ADL. Thanks for your help. – Alex Martin May 19 '18 at 06:14
1

I found the following workaround, refactoring the overload as a method of one of the nested classes:

template<typename Type> class Outer
{
public:
    class InnerA;
    class InnerB;
};


template<typename Type> class Outer<Type>::InnerA
{
    const int val = 0;
public:
    bool operator!=(const typename Outer<Type>::InnerB& other);
};


template<typename Type> class Outer<Type>::InnerB
{
    const int val = 1;
    friend bool Outer<Type>::InnerA::operator!=(const InnerB& other);
};


template<typename Type> bool Outer<Type>::InnerA::operator!=(const typename Outer<Type>::InnerB& other)
{
    return val != other.val;
}


int main()
{
    bool b = Outer<void>::InnerA() != Outer<void>::InnerB();
}

However, I am still curious if the same can be accomplished using a non-member friend function as in the question.

Alex Martin
  • 31
  • 1
  • 6
  • 1
    *"I am still curious if the same can be accomplished using a non-member friend function as in the question."*: With `Outer::InnerA`, `Type` cannot be deduced. you might use `Outer::InnerA().operator !=(Outer::InnerB())`. Non template friend functions is the solution (for ADL and making deduction a non problem). – Jarod42 May 17 '18 at 08:51
  • @Jarod42 I didn't understand your reply when I first read it, but I just found a paper that confirms your statement and explains why. I'll make a new answer to reflect this. Thanks! – Alex Martin May 21 '18 at 05:14
0

The issue with the code in the question ended up being that template deduction is not attempted on type names nested inside a dependent type (e.g. Outer<Type>::Inner).

This question is essentially a duplicate of Nested template and parameter deducing. A detailed explanation on why this is a problem can be found here.

Alex Martin
  • 31
  • 1
  • 6