如何在不与标准库运算符冲突的情况下为一组相关的 类 模板重载运算符?

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

这似乎是一个相当直接的想法:我有一组 classes,我应该能够为它们编写一个运算符,比方说减法,使用基本上完全相同的代码。

尝试以 "obvious" 方式执行此操作时,即:

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

然后,在某些编译器上,这似乎与迭代器的减法运算符冲突(特别是它在 gcc 3.4.6 和 Apple LLVM 上中断,而在 gcc 版本 4 或更新版本上似乎工作正常),我得到了以下错误:

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

我还考虑过仅对基 class 进行模板重载,组中的所有 classes 都将派生自该基,但是由于第一个参数是按值传递的,所以我认为特定于 subclass 的信息会在复制过程中丢失。

我是不是遗漏了什么明显的东西?有没有办法让所有这些编译器都满意?

编辑:理想情况下,该解决方案应该适用于 C++03。

将此模板和您的自定义数据结构放在同一个命名空间中(如果它们在根命名空间中 - 只需移动它们)。

由于 argument-dependent lookup,编译器将在您的结构的命名空间中查找此运算符,并且找到它没有问题和歧义。

在 C++11 中,std::enable_if and std::is_base_of 可能对您有所帮助。假设你有一个基数 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;
}

此处您的运算符只会被考虑用于派生自 Base 的类型 T

编辑:

cppreference.com 的链接页面显示了可能的实现,因此您也可以在 C++03 中使用它们,只需实现您自己的 enable_ifis_base_of.

使用基础 class、adl koenig 运算符和 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;
    }
  };
}

现在继承自 ops::subtract_support 使 - 适合您的类型。 (注意两行正文:确保 lhs 移出 -,与您在 OP 中的版本不同)。

仅名称space 限制会导致您的 - 被发现任何模板生成的类型,其中一个 tge 参数来自您的名称space,以及其他意外情况:adl 操作模板成员来自特定名称的类型space.1

此技巧可让您在声明时将每种类型标记为使用此技术。

这种技术对二进制布局的影响几乎为零。但是,如果您需要一些晦涩的东西,例如前缀的布局兼容性,则可能需要特征 class。


现在,这是一个 C++11 解决方案。显然,OP 需要 C++03。好吧,最好的方法是像 C++11 一样实现它,这样当您升级编译器时就可以删除代码。

enable_if 可以很容易地用 C++03 编写。 is_base_of 需要多几行:

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;
  };
}

我们还需要调整模板 ADL 运算符中的 SFINAE 以符合 C++03:

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 例如,如果 Foo 是名称中的类型 space 具有贪婪的 - 模板运算符,则 decltype(v0-v1) 其中 v0v1vector<Foo> 将是 vector<Foo>。这是一个误报(它不会编译)。但是 vec3<Foo>(向量 space of 3 Foo)和它自己的 - 会导致同样的歧义。

遍历 您可以使用带有一个附加特征的简单 sfinea 来完成。 c++03 兼容代码:

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]