The following codes related to conditional expression:
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>());
got an error like this:
error: operands to ?: have different types
‘std::greater<unsigned char>’ and ‘std::less<unsigned char>’
Is this a compiler bug? In my instinct, the two functor should have the same type.
std::greater
and std::less
are different types (just as different as std::string
and std::unordered_map
). They only provide same interface (operator()
).
Conditional operator cond ? expr1 : expr2
requires, that expr1
and expr2
is of the same type (or one is implicitly convertible to another) and that is not your case.
You can use
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>{});
Or if you do not want to have two almost same lines, you can help yourself with generic 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>{});
Functors in C++ do not traditionally share a type. This makes it easy for the compiler to understand exactly which functor is being called, and causes a performance boost.
There is a mechanism in C++ called type erasure. This is where you forget what makes two types different, and only remembers what makes them the same.
The most common kind of type erasure in C++ is std::function
. This function here type erases a stateless template that takes two T const&
and returns a bool
:
template<class T, template<class...>class Z>
std::function< bool(T const&, T const&) >
comp() { return Z<T>{}; }
we can then take your code and do this:
uchar *itr = std::upper_bound(start, end, uchar(20),
cond? comp<uchar, std::greater>() : comp<uchar, std::less>());
or alternatively:
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);
};
}
which gives you function pointers that call less
or greater
as required. This version might be slightly easier for compilers to optimize than the std::function
one, as compilers are really good at optimizing constant function pointers.
Functors are just regular types. std::greater<uchar>
is a completely different type from std:less<uchar>
. Just like how std::vector<uchar>
is different from std::set<uchar>
, even though their properties are similar!
You can achieve what you want to achieve if you move your ternary operator a bit though:
char *itr = cond ?
std::upper_bound(start, end, uchar(20), std::greater<uchar>()) :
std::upper_bound(start, end, uchar(20), std::less<uchar>());
It's slightly more duplication, but just as readable and will compile.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With