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\$
  • \$\begingroup\$ All containers have an internal type indicating the type they contain: vec::value_type \$\endgroup\$ – Martin York May 15 '13 at 18:21
  • \$\begingroup\$ @LokiAstari: MSVC++10 complains "A name followed by :: must be a class or namespace name." \$\endgroup\$ – Felix Dombek May 15 '13 at 18:30
  • \$\begingroup\$ See below: For value_type \$\endgroup\$ – Martin York May 15 '13 at 18:35
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
    \$\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\$ – Martin York May 16 '13 at 11:50
  • \$\begingroup\$ @LokiAstari Good point. Added. \$\endgroup\$ – Yuushi May 16 '13 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\$
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\$
  • \$\begingroup\$ Of course, this is how I did it, still not optimal. \$\endgroup\$ – Felix Dombek May 17 '13 at 13:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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