Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

c++11

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.

like image 252
Robin Hsu Avatar asked Sep 28 '16 16:09

Robin Hsu


3 Answers

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>{});
like image 170
Zereges Avatar answered Nov 16 '22 02:11

Zereges


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.

like image 25
Yakk - Adam Nevraumont Avatar answered Nov 16 '22 01:11

Yakk - Adam Nevraumont


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.

like image 30
Karl Nicoll Avatar answered Nov 16 '22 01:11

Karl Nicoll