c++ 11对std::greater和std::less的条件表达式得到了不同类型的错误

C++11 conditional expression on std::greater and std::less got error of different types

本文关键字:std 错误 同类型 表达式 条件 greater less c++      更新时间:2023-10-16

与条件表达式相关的代码如下:

typedef unsigned char uchar;
uchar data[100];
// assign something to array[] here
uchar *start = data;
uchar *end = data+100;
bool cond = f();  // f() could return true or false
uchar *itr = std::upper_bound(start, end, uchar(20), 
                cond? std::greater<uchar>() : std::less<uchar>());

出现如下错误:

error: operands to ?: have different types 
‘std::greater<unsigned char>’ and ‘std::less<unsigned char>’

这是一个编译器错误吗?根据我的直觉,这两个函子应该具有相同的类型。

std::greaterstd::less是不同的类型(就像std::stringstd::unordered_map一样)。它们只提供相同的接口(operator())。

条件运算符cond ? expr1 : expr2要求expr1expr2是同一类型(或者一个可以隐式转换为另一个),而这不是您的情况。

可以使用

char *itr;
if (cond)
    itr = std::upper_bound(start, end, uchar(20), std::greater<uchar>{});
else
    itr = std::upper_bound(start, end, uchar(20), std::less<uchar>{});

或者,如果你不想有几乎相同的两行,你可以帮助自己使用泛型lambda

auto upper_bound = [&](const auto& cmp){
    return std::upper_bound(start, end, uchar(20), cmp);
};
if (cond)
    itr = upper_bound(std::greater<uchar>{});
else
    itr = upper_bound(std::less<uchar>{});

c++中的函子传统上不共享类型。这使得编译器很容易理解确切地是哪个函函数被调用,并导致性能提升。

在c++中有一种叫做类型擦除的机制。在这里,您忘记了使两种类型不同的原因,而只记得使它们相同的原因。 c++中最常见的类型擦除是std::function。这里的函数类型擦除一个无状态模板,该模板接受两个T const&并返回一个bool:
template<class T, template<class...>class Z>
std::function< bool(T const&, T const&) >
comp() { return Z<T>{}; }

我们可以把你的代码,并这样做:

uchar *itr = std::upper_bound(start, end, uchar(20), 
            cond? comp<uchar, std::greater>() : comp<uchar, std::less>());

或者:

template<class T>
using comp_sig = bool(T const&, T const&);
template<class T, template<class...>class Z>
comp_sig<T>* comp() {
  return [](T const& lhs, T const& rhs)->bool{
    return Z<T>{}(lhs, rhs);
  };
}

为您提供了根据需要调用lessgreater的函数指针。对于编译器来说,这个版本可能比std::function版本更容易优化,因为编译器非常擅长优化常量函数指针。

函子只是常规类型。std::greater<uchar>std:less<uchar>是完全不同的类型。就像std::vector<uchar>不同于std::set<uchar>一样,尽管它们的属性相似!

你可以实现你想要实现的,如果你移动你的三元操作符:

char *itr = cond ?
    std::upper_bound(start, end, uchar(20), std::greater<uchar>()) :
    std::upper_bound(start, end, uchar(20), std::less<uchar>());

它稍微多了一些重复,但是同样可读并且可以编译。

我认为不是,因为它是不同的类型。Std::less是这样的:

template <class T>
struct Less
{
      bool operator()(const T & t1, const T & t2) ..
};

首先,这两个函函数不具有相同的类型,尽管它们具有相同类型的操作符(),即在模板实例化之后,

bool operator()(uchar lhs, uchar rhs)  

同样,仅仅因为vector<int>deque<int>具有相同类型的操作符[]显然并不意味着它们具有相同的类型。

回到你的代码,如果你能使用c++ 11,一个简单的&优雅的解决方案是使用lambda:

char *itr = std::upper_bound(start, end, uchar(20),  
            [cond](uchar lhs, uchar rhs) { return cond ? lhs > rhs : lhs < rhs); }

否则,就直接写一个函子

class F{  
public:  
    explicit F(bool cond): m_cond(cond) {}
    bool operator(uchar lhs, uchar rhs) { return m_cond ? lhs > rhs : lhs < rhs); }  
private:
    bool m_cond;
};

最后,lambda是一个非常方便的工具,我建议你多了解它。
c++ 11中的lambda表达式是什么?
何时在lambda

上使用函子

将它们都转换为lambdas(非捕获,非泛型)。在这种情况下,它们的类型仍然不同,但它们都自然地转换为普通的旧函数指针bool (*) (uchar, uchar),因此?:可以工作

uchar *itr = std::upper_bound(start, end, uchar(20),
                 cond?
                 ([](uchar x, uchar y){return std::greater<uchar>{}(x,y);})
                 :
                 ([](uchar x, uchar y){return std::less   <uchar>{}(x,y);})
             );

(这在clang 3.5和g++ 5.3上适用,但我不确定它的可移植性如何。我应该使用一元+强制转换到函数指针吗?