How do I template overload an operator for a group of related classes without conflicting with standard library operators?

痴心易碎 提交于 2019-12-07 12:31:45

问题


This seems like a fairly straight forward idea: I have a group of classes for which I should be able to write an operator, let's say subtraction, using basically the exact same code.

When trying to do this the "obvious" way, i.e:

template <typename T>
T operator-(T a, const T& b) {
    return a -= b;
}

Then, on certain compilers, this seems to conflict with a subtraction operator for iterators (specifically it breaks on gcc 3.4.6 and Apple LLVM, while seemingly working fine on gcc version 4 or newer), and I get the following error:

 error: use of overloaded operator '-' is ambiguous (with operand types 'std::__1::__wrap_iter<const double *>' and 'const_iterator'
      (aka '__wrap_iter<const_pointer>')) 

I also considered template overloading for just a base class from which all of the classes in the group would be derived from, but then since the first parameter is passed by value, I think information specific to the subclass would be lost during the copy.

Am I missing something obvious? Is there a way to do this that keeps all these compilers happy?

Edit: the solution should ideally work in C++03.


回答1:


Use a base class, adl koenig operators, and sfinae.

namespace ops{
  struct subtract_support{
    template<class T,
      std::enable_if_t<std::is_base_of<subtract_support, T>{}, int> =0
    >
    friend T operator-( T lhs, T const& rhs ){
      lhs-=rhs;
      return lhs;
    }
  };
}

Now inheriting from ops::subtract_support causes - to work for your type. (Note the two-line body: that ensures the lhs is moved out of -, unlike your version in the OP).

Merely namespace restricting results in your - being found any template produced type where one of tge arguments comes from your namespace, and other unintended cases: adl operates on types whose template members come from a specific namespace.1

This trick lets you mark each type as using this technique at the point of declaration.

There are almost zero binary layout implications of this technique. But if you need something obscure like layout compatibility of prefixes a traits class may be required instead.


Now, this is a C++11 solution. Apparently the OP needs C++03. Well, the best way forward is to implement it as much like C++11 so code can be deleted when you upgrade your compiler.

enable_if can easily be written in C++03. is_base_of takes a few more lines:

namespace notstd{
  namespace details{
    template<class T, class U>
    struct base_test{
      typedef char no; // sizeof(1)
      struct yes { no unused[2]; }; // sizeof(2) or greater
      static yes test(T*); // overload if arg is convertible-to-T*
      static no test(...); // only picked if first overload fails
      // pass a `U*` to `test`.  If the result is `yes`, T
      // is a base of U.  Note that inaccessible bases might fail here(?)
      // but we are notstd, good enough.
      enum {value= (
        sizeof(yes)==sizeof(test((U*)0))
      )};
    };
  }
  template<class Base, class T>
  struct is_base_of{
    enum{value=details::base_test<Base,T>::value};
  };
  template<bool b, class T=void>
  struct enable_if {};
  template<class T>
  struct enable_if<true, T> {
    typedef T type;
  };
}

We also need to tweak the SFINAE in the template ADL operator for C++03 compliance:

namespace ops{
  struct subtract_support{
    template<class T>
    friend
    typename notstd::enable_if<notstd::is_base_of<subtract_support, T>::value, T>::type
    operator-( T lhs, T const& rhs ){
      lhs-=rhs;
      return lhs;
    }
  };
}

Live example.


1 As an example, if Foo is a type in the namespace with a greedy - template operator, then decltype(v0-v1) where v0 and v1 are vector<Foo> will be vector<Foo>. That is a false positive (it will not compile). But a vec3<Foo> (vector space of 3 Foo) with its own - would result in the same ambiguity.




回答2:


Put this template and your custom data structures in same namespace (if they are in root namespace - just move them).

Because of argument-dependent lookup, compiler will look for this operator in namespace of your structures, and find it without problems and ambiguity.




回答3:


In C++11, std::enable_if and std::is_base_of may have helped you. Given you have a base class Base:

template <typename T, typename = std::enable_if_t<std::is_base_of<Base, T>::value>>
T operator-(T a, const T& b) {
        return a -= b;
}

Here your operator will only be considered for types T derived from Base.

EDIT:

The linked pages of cppreference.com show possible implementations, so you can use them in C++03 as well by just implementing your own enable_if and is_base_of.




回答4:


Traversing Yakk's answer you could do it using simple sfinea with just one additional trait. c++03 compliant code:

namespace ops{
  struct subtract_support{
    template <class T>
    struct subtract_support_trait {
        typedef T type;
    };
    template<class T>
    friend typename T::template subtract_support_trait<T>::type operator-( T lhs, T const& rhs ){
      lhs-=rhs;
      return lhs;
    }
  };
}

[live demo]



来源:https://stackoverflow.com/questions/40763492/how-do-i-template-overload-an-operator-for-a-group-of-related-classes-without-co

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!