如何确定一个类是否实现!=运算符重载?

时间:2016-03-30 15:34:26

标签: c++

给出以下功能:

template <typename T>
static bool equals(const std::vector<T> &a, const std::vector<T> &b) {
  if (a.size() != b.size())
    return false;

  for (size_t i = 0; i < a.size(); ++i)
    if (a[i] != b[i]) // Requires that the != operator is supported by the template type.
      return false;

  return true;
}

如何确定给定的T是否覆盖!=运算符?如果有人使用没有重载的类型,它可能最终会使用简单的二进制比较,这可能会导致错误的结果。所以我想确保只能在这里使用具有自己的!=运算符重载的类。

6 个答案:

答案 0 :(得分:5)

[更新1 - 提升&#39;半&#39;溶液]

我意识到,如果你的类有转换操作符(对于允许!= comparision的类型),我的第一个答案中的示例不起作用,要修复它你可以使用boost has_not_equal_to类型特征。仍然不完美,因为在某些情况下它会产生编译错误而不是给出值。这些案例列在提供的链接中。

[更新2 - 概念解决方案]

使用概念的示例:

#include <iostream>
#include <vector>

template<typename T>
concept bool OperatorNotEqual_comparable()
{
    return requires (T a, T b) {
        { a.operator!=(b) } -> bool;
    };   
}

template <OperatorNotEqual_comparable T>
static bool equals(const std::vector<T> &a, const std::vector<T> &b) {
  if (a.size() != b.size())
    return false;

  for (size_t i = 0; i < a.size(); ++i)
    if (a[i] != b[i]) // Requires that the != operator is supported by the template type.
      return false;

  return true;
}

struct sample1{
    bool operator!=(const sample1&) const { return true; }
};

struct sample2{
};

struct sample3{
    operator void*() { return 0; }
};

int main() {
    // Compiles ok!
    std::vector<sample1> vec1;
    equals(vec1, vec1);

    // Fails, which is OK!
    //std::vector<sample2> vec2;
    //equals(vec2, vec2);    

    // Fails, which is OK!
    //std::vector<sample2*> vec2;
    //equals(vec2, vec2);

    // Fails, which is OK!
    //std::vector<int> vec4;
    //equals(vec4, vec4);        

    // Fails, which is OK!
    //std::vector<sample3> vec5;
    //equals(vec5, vec5);            
}

http://melpon.org/wandbox/permlink/txliKPeMcStc6FhK

[旧答案 - SFINAE解决方案,不检查转换运算符]

您可以使用SFINAE,并在不久的将来概念(它们在gcc 6.0中),

#include<iostream>
#include<string>
#include<type_traits>

template<typename T>
class has_not_equal{
    template<typename U>
    struct null_value{
        static U& value;
    };

    template<typename U>
    static std::true_type test(U*,decltype(null_value<U>::value!=null_value<U>::value)* = 0);
    static std::false_type test(void*);

public:
    typedef decltype(test(static_cast<T*>(0))) type;
    static const bool value = type::value;
};

struct sample1{
    bool operator!=(const sample1&) { return true; }
};

struct sample2{
};

int main(){
    std::cout<<std::boolalpha;
    std::cout<<has_not_equal<int>::value<<std::endl;
    std::cout<<has_not_equal<std::string>::value<<std::endl;

    std::cout<<has_not_equal<sample1>::value<<std::endl;
    std::cout<<has_not_equal<sample2>::value<<std::endl;
}

输出:

g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
true
true
true
false

live

上面的代码是此site的修改版本,适用于operator==,我将其更改为operator!=

答案 1 :(得分:1)

如果T没有operator !=,那么模板将不会针对该类型进行实例化,但是您将从编译器中获得(可能非常长且不可读)错误消息。另一方面,它Toperator !=,那么使用它应该没问题。除非T s operator !=无论如何被打破,否则不会出现无声的错误结果。

答案 2 :(得分:1)

唯一的另一个(除了转换)情况(我能想到)可以发生二进制比较(定义{{​​1}})并导致无效的错误结果是operator!=实际上是一个指针而且你期望进行“深度比较”。

可以为包含指针的向量添加重载,但不会覆盖指向数组存储的指针。

T

答案 3 :(得分:1)

如果您有兴趣检查是否有一个操作员!=在一个类中定义(并且它已经准确地给出了签名),您可能需要这种方法(测试来自marcinj的代码):

#include <iostream>
#include <vector>
#include <type_traits>

template <class T, class = void>
struct has_not_equal: std::false_type { };

template <class T>
struct has_not_equal<T, typename std::enable_if<std::is_same<decltype(static_cast<bool (T::*)(const T&)const>(&T::operator!=)), bool (T::*)(const T&)const>::value>::type >: std::true_type { };

struct sample1{
    bool operator!=(const sample1&) const { return true; }
};
struct sample2{
};
struct sample3:sample2 {
  bool operator!=(const sample2& b) const { return true; }
};

struct sample4:sample2 {
  bool operator!=(const sample2& b) const { return true; }
  bool operator!=(const sample4& b) const { return true; }
};

int main(){

    std::cout<<std::boolalpha;
    std::cout<<has_not_equal<int>::value<<std::endl;
    std::cout<<has_not_equal<std::string>::value<<std::endl;

    std::cout<<has_not_equal<sample1>::value<<std::endl;
    std::cout<<has_not_equal<sample2>::value<<std::endl;
    std::cout<<has_not_equal<sample3>::value<<std::endl;
    std::cout<<has_not_equal<sample4>::value<<std::endl;
}

计划的输出:

false
false
true
false
false
true

您可以通过添加has_not_equal结构的特化来轻松添加允许的operator!=重载...

答案 4 :(得分:1)

即使我的问题的标题在这里说我正在寻找一种方法来确定是否定义了!=运算符,真正的问题(正如你可以从描述和我的评论中读到的)是如何确保我对于没有定义!=运算符的类型T,不要默默地得到错误的结果。现有的答案带来了好处,但真正的答案是:

1)如果使用缺少if (a[i] != b[i])运算符覆盖的类型(例如!=)来实例化具有值类型数组的模板,则会在std::vector<sample2>行上出现编译器错误marcinj的答案)或者如果你使用std::equal,你会很难理解编译器错误。在这种情况下,在寻找解决方案时,显式比较会更有帮助。

2)如果要在向量中有一个引用类型进行比较,你首先会得到任何问题(因为有为引用定义的比较运算符,即使它们只通过地址值进行平面比较)。如果您希望确保比较的工作方式就像您使用了值类型(包括深度比较)那么为Pixelchemist指出的向量添加一个带指针类型的特化。但是,我现在无法编译std :: equals变种。

最后,对我有用的解决方案是:

template <typename T>
static bool equals(const std::vector<T> &a, const std::vector<T> &b) {
  if (a.size() != b.size())
    return false;

  for (size_t i = 0; i < a.size(); ++i)
    if (a[i] != b[i])
      return false;

  return true;
}

template <typename T>
static bool equals(const std::vector<T*> &a, const std::vector<T*> &b) {
  if (a.size() != b.size())
    return false;

  for (size_t i = 0; i < a.size(); ++i)
    if (*a[i] != *b[i])
      return false;

  return true;
}

使用marcinj的答案中的sample1 / 2/3/4结构进行测试。重要的是要注意,运算符重载必须使用值类型(operator == (const sample1&))而不是引用类型。

我仍然赞成所有给出有用信息的答案,以获得最终答案。

答案 5 :(得分:0)

  

如果有人使用没有重载的类型,最终可能会使用简单的二进制比较,这可能会导致错误的结果。

在C ++中没有“二进制比较”这样的东西。您的类/结构具有operator!=或模板将不会被实例化(如果没有其他候选项,那么它将是一个错误)。