【问题标题】:How do I template overload an operator for a group of related classes without conflicting with standard library operators?如何模板重载一组相关类的运算符而不与标准库运算符冲突?
【发布时间】:2016-11-23 11:37:57
【问题描述】:

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

当尝试以“明显”的方式执行此操作时,即:

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>')) 

我还考虑过模板重载只是一个基类,组中的所有类都将从该基类派生,但是由于第一个参数是按值传递的,我认为特定于子类的信息会在复制。

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

编辑:理想情况下,该解决方案应该在 C++03 中运行。

【问题讨论】:

    标签: c++ templates operator-overloading c++03


    【解决方案1】:

    使用基类、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 中的版本不同)。

    仅命名空间限制会导致您的 - 被发现任何模板生成的类型,其中 tge 参数之一来自您的命名空间,以及其他意外情况:adl 对模板成员来自特定命名空间的类型进行操作。1

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

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


    现在,这是一个 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 是命名空间中带有贪婪- 模板运算符的类型,那么decltype(v0-v1) 其中v0v1vector&lt;Foo&gt;将是vector&lt;Foo&gt;。这是一个误报(它不会编译)。但是带有自己的-vec3&lt;Foo&gt;(3 Foo 的向量空间)会导致同样的歧义。

    【讨论】:

    • 啊,抱歉,我应该提到它需要使用 C++03。
    • 引人入胜的笔记,如果我可以这么说的话。我赞成这个问题来跟踪这个答案。
    • 对于无法继承的类型,您将如何约束它? (我主要考虑enums)
    • @story 特征类,或(我的偏好)adl-lookup 特征函数。所以constexpr std::true_type supports_x(tag_t&lt;some_enum&gt;){return {};},可能由宏(ik)定义,用来保护操作员。想要启用该功能的枚举可以声明他们的支持。不太完美,因为仍然需要让运算符在范围内才能使用它,这与将其拖入的继承/命名空间技巧不同。
    • @ChrisUsher C++03 支持添加
    【解决方案2】:

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

    因为argument-dependent lookup,编译器会在你的结构的命名空间中寻找这个操作符,并且没有问题和歧义地找到它。

    【讨论】:

    • 迄今为止最干净的解决方案
    • 不够好:试试v0-v1,它们是相关类型的向量。
    • @Yakk,这会导致错误。这是我所期望的,因为向量根本不支持这个操作。所以你还没有完全表达你的观点。
    • @story 任何支持- 的模板生成类型将T 作为模板参数都会发现这个贪婪的- 并生成虚假错误。如果它不支持-,sfinae 测试会认为它支持:decltype(v0-v1)std::vector&lt;T&gt;!除非触发“令牌”的查找是模糊的 - 不符合条件,否则永远不要定义这样的贪婪模板。
    【解决方案3】:

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

    【讨论】:

    • 啊,对不起,我应该提到它需要与 C++03 一起工作。
    • cppreference.com 的链接页面显示了可能的实现,因此您也可以在 C++03 中使用它们,只需实现自己的 enable_ifis_base_of ;)
    • @W.F.会的,放心
    • @StoryTeller 是吗? example
    • @W.F.看中了,很好抓。我不记得函数模板的限制了。
    【解决方案4】:

    遍历Yakk's answer 你可以使用简单的 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]

    【讨论】:

    • 笑,这有点好笑。聪明的!在 C++11 中,如果我们非公开地继承自 subtract_support,我认为您的将不支持 -is_base_of SFINAE 会吗?不确定。
    • 哎呀,继承是对的...当我回到电脑前,我会进行编辑...谢谢!
    • @Yakk 等等...我不确定作为简单特征创建的is_base_of 是否可以测试非公共继承...甚至可以在 C++ 中为这个用例完成03?需要再考虑一下...
    猜你喜欢
    • 1970-01-01
    • 2021-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-29
    • 2019-03-03
    • 2013-02-05
    • 1970-01-01
    相关资源
    最近更新 更多