8
\$\begingroup\$

How can I eliminate repetition from this code?

std::vector<std::wstring> vec;
bool                      descending;
if (descending)
{
    std::sort(vec.begin(),
              vec.end(),
              std::greater<std::wstring>());
}
else 
{
    std::sort(vec.begin(),
              vec.end(),
              std::less<std::wstring>());
}

I tried

std::sort(vec.begin(),
          vec.end(),
          descending 
             ? std::greater<std::wstring>()
             : std::less<std::wstring>());

but it fails because greater and less do not evaluate to the same type.

Another question: is it possible to automatically retrieve the required type, std::wstring, from the object vec?

\$\endgroup\$
3
  • \$\begingroup\$ All containers have an internal type indicating the type they contain: vec::value_type \$\endgroup\$ May 15, 2013 at 18:21
  • \$\begingroup\$ @LokiAstari: MSVC++10 complains "A name followed by :: must be a class or namespace name." \$\endgroup\$ May 15, 2013 at 18:30
  • \$\begingroup\$ See below: For value_type \$\endgroup\$ May 15, 2013 at 18:35

4 Answers 4

5
\$\begingroup\$

There are a few ways of going about this. One is creating an aggregate type that can forward to the call to the correct comparator.

template <typename T>
struct comp
{
private:
    bool desc;
    std::greater<T> great;
    std::less<T> less;

public:
    explicit comp(bool descending = false)
      : desc(descending)
    { }

    bool operator()(const T& f, const T& g) const
    {
        return (desc) ? great(f, g) : less(f, g);
    }
};

The downside of this is that a comp object will be at least sizeof(greater + less + bool). This may or may not be a problem. Your call to sort would then look something like:

typedef std::wstring str_t;
bool descending = true;
std::sort(vec.begin(), vec.end(), comp<str_t>(descending));

If the decision is always known at compile time, you can play some template tricks:

template <typename T, bool desc>
struct comp;

template <typename T>
struct comp<T, true>
{
private:
    std::greater<T> great;

public:
    bool operator()(const T& f, const T& g) const
    {
        return great(f, g);
    }
};

template <typename T>
struct comp<T, false>
{
private:
    std::less<T> less;

public:
    bool operator()(const T& f, const T& g) const
    {
        return less(f, g);
    }
};

This requires descending to be const or constexpr:

constexpr bool descending = false;
std::sort(v.begin(), v.end(), comp<str_t, descending>());

This might be a one step forward, two step back kind of solution. It's quite a bit of code to avoid an if/else - you'll need to decide if this is worth it at all.

Edit: As @LokiAstari pointed out, you can simplify the above with inheritance:

template <typename T> 
struct comp<T, true>
  :  std::greater<T>
{ }

template <typename T> 
struct comp<T, false>
  : std::less<T> 
{ }
\$\endgroup\$
2
  • 2
    \$\begingroup\$ Last example can be simplified to: template <typename T> struct comp<T, true>: std::greater<T> {} and template <typename T> struct comp<T, false>: std::less<T> {} \$\endgroup\$ May 16, 2013 at 11:50
  • \$\begingroup\$ @LokiAstari Good point. Added. \$\endgroup\$
    – Yuushi
    May 16, 2013 at 12:05
3
\$\begingroup\$

not an answer but extended comment that needs space:

 typedef std::vector<std::wstring>   Vec;
 Vec vec;

 std::sort(vec.begin(), vec.end(), std::greater<Vec::value_type>());
\$\endgroup\$
2
\$\begingroup\$

Using std::function seems to work here with g++ 4.5.2 and -std=c++0x:

#include <algorithm>
#include <functional>
#include <iostream>
#include <string>
#include <vector>

int main(int argc, char* argv[]) {
  std::function<bool(std::string,std::string)> cmp; 
  if (false)
    cmp = std::greater<std::string>();
  else
    cmp = std::less<std::string>();

  std::vector<std::string> vec = { "2", "1", "3" };
  std::sort(vec.begin(), vec.end(), cmp);

  for (int i = 0; i < vec.size(); ++i) {
    std::cout << vec[i] << std::endl;
  }
}

You can also use the ternary operator if you cast your compare objects to the same type:

  typedef std::function<bool(std::string,std::string)> cmp_t;
  const auto cmp =
    true ? static_cast<cmp_t>(std::greater<std::string>()) :
           static_cast<cmp_t>(std::less<std::string>());
\$\endgroup\$
0
1
\$\begingroup\$

Just create a simple helper function template:

template<typename Iter>
void sort_with_order(Iter i0, Iter i1, bool descending)
{
  typedef typename std::iterator_traits<Iter>::value_type value_type;

  if (descending)
  {
    std::sort(i0, i1, std::greater<value_type>());
  }
  else 
  {
    std::sort(i0, i1, std::less<value_type>());
  }
}

Then call it like this:

std::vector<std::wstring> vec;
bool                      descending;

// ...

sort_with_order(vec.begin(), vec.end(), descending);
\$\endgroup\$
1
  • \$\begingroup\$ Of course, this is how I did it, still not optimal. \$\endgroup\$ May 17, 2013 at 13:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.