7

I want to be able to write

if (3 <= X <= 10)
{

}
else if (20 < X < 100) 
{ //...etc

in C++ and have it evaluate correctly. I know you can do this in Python and I think it's a very readable way to express a conditional.

I don't want to have to write:

if (3 <= X && X <= 10) //etc.

How can I do this in C++? Is it possible? What would overloading the operators look like? If not, could you explain why it's not possible?

Casey Patton
  • 4,021
  • 9
  • 41
  • 54
  • 1
    When you say simulate, do you mean you want to use some kind of operator overload so that the above syntax works? – rurouniwallace Sep 30 '12 at 02:26
  • 3
    I don't think hacking around this is gonna make code any cleaner... – Mysticial Sep 30 '12 at 02:27
  • Python can do it. This invalidates many excuses for why C++ can not. – updogliu Sep 30 '12 at 02:29
  • @phoenixheart6 yes. Doesn't need to be an operator overload...any way you can think of. I couldn't come up with a way but figured the broader c++ community might know of one. – Casey Patton Sep 30 '12 at 02:36
  • 1
    I think most of the solutions here, certainly all recommending operator overloading for this, are violating rule #1 and #2 of the [The Three Basic Rules of Operator Overloading in C++](http://stackoverflow.com/a/4421708/140719). I am with [FredOverflow on this](http://stackoverflow.com/a/12661339/140719): This is overkill and just obfuscates the code. – sbi Sep 30 '12 at 16:39

7 Answers7

11

Are you sure you need this?

[UPDATE]

After a while I come to an idea which even does not look totally crazy ;)

I made a working example at ideone

You need to start with wrapping of the first element:

int main() {
   int x = 134, y = 14;
   if (IntComp(7) <= x <= 134)
   {
       std::cout << "Hello ";
   } 
   if (IntComp(134) > y > 12)
   {
       std::cout << "world!";
   } 
}

The magic here:

class IntComp {
public:
   IntComp(int x, bool result = true) : value(x), result(result) {}
   IntComp operator <= (int x) const
   {
       return IntComp(x, result && value <= x);
   }
   IntComp operator > (int x) const
   {
       return IntComp(x, result && value > x);
   }
   operator bool() const { return result; }
private:
   int value;
   bool result;
};
PiotrNycz
  • 23,099
  • 7
  • 66
  • 112
  • Definitely don't need it...just a curiosity really. – Casey Patton Sep 30 '12 at 02:41
  • @CMan your question is really good, I am starting to think that with my new version (I see others came to very similar idea) it is quite useful... – PiotrNycz Sep 30 '12 at 03:05
  • Cute idea. Unfortunately, this solution allows for expressions such as `IntComp(32) < x < 43 > 10 < -3 < 5 > 10;` to make total sense. – Alex Pana Apr 19 '14 at 21:31
  • @AlexPana You are right, however I believe I could fix it, so only `IntComp(32) < x < 43` or more general `IntComp(Value) OP1 VAR OP2 CONSTANT` is possible, where {OP1,OP2} are in `{{<,<},{<.<=},{<=,<},{>,>}....}` set. That would require quite a lot work, and since this answer is more for fun than for anything useful - then forgive me, but I am giving up... – PiotrNycz Apr 23 '14 at 20:04
6

You can't do this in C++. You have to break it up into two separate operations:

if (3 <= X && X <= 10)
{
    ...
}
else if (20 < X && X < 100)
{
    ...
}
Marlon
  • 19,924
  • 12
  • 70
  • 101
  • 3
    I think he knows that but he's just looking for a way to do it anyway. – David G Sep 30 '12 at 02:27
  • 3
    I still think this is the right answer. If you are going to write C++ then use the syntax that everyone understands. When using operator overloading "do as the ints do". – Blastfurnace Sep 30 '12 at 03:27
  • "do as the ints do" -- that's a good rule of thumb. But we shouldn't follow rules blindly. There may be a context in which it is useful to do something different. Maybe you are creating a domain-specific language, for example. So yes, we would probably frown on some new guy adding this to our corporate accounting system (or many other types of communal projects), but if someone wants to do this in their own personal project, or if they are trying to invent a new programming paradigm -- more power to them! – Brent Bradburn Sep 30 '12 at 05:53
  • 1
    @Blastfurnace - Well, I'm still in college...perfect time to do some crazy stuff ;) Besides, I don't think creating the syntax: 10 < X < 100 would confuse anyone that much. Seems far more intuitive than the way it's currently done in C++... – Casey Patton Sep 30 '12 at 09:41
6

Personally, I think all these operator overloading solutions are a bit over-engineered. How about two simple function templates instead?

template<typename A, typename B, typename C>
bool ordered(const A& a, const B& b, const C& c)
{
    return (a <= b) && (b <= c);
}

template<typename A, typename B, typename C>
bool between(const A& a, const B& b, const C& c)
{
    return (a < b) && (b < c);
}

void foobar(int X)
{
    if (ordered(3, X, 10))
    {

    }
    else if (between(20, X, 100))
    {
        // ...
    }
}
fredoverflow
  • 256,549
  • 94
  • 388
  • 662
  • A `range` template with suitable factory functions is very nice for C++ range based `for`, and with a simple member function `contains` can do this thing very naturally also. Then, *one* generally very useful little thingy, instead of umpteen. But personally, so far I just write the conjunction (I think it has to do with Visual C++ not support the C++ range based for loop until quite recently, and I've been too ill to do much C++ stuff, like, maintain my blogs). – Cheers and hth. - Alf Sep 30 '12 at 13:38
4

It is amazing, some of the things that C++ will let you do with user-defined types...

struct C
   {
   int value;
   bool state;
   C( int value, bool state=true ) : value(value), state(state) {}
   };

C operator <= ( int a, C b )
   {
   if ( a <= b.value ) return C(b.value,true);
   return C(b.value,false);
   }

bool operator <= ( C a, int b )
   {
   if ( a.state && a.value <= b ) return true;
   return false;
   }

std::ostream & operator << ( std::ostream & os, C c ) { os<<c.value; return os; }

void test( C X )
   {
   if (3 <= X <= 10) cerr<<"(3 <= X <= 10) is true for X="<<X<<"\n";
   }

int main()
   {
   test(2), test(3), test(4), test(10), test(11);
   }

Output...

$ ./how-to-implement-3-x-10-in-c++.cpp
(3 <= X <= 10) is true for X=3
(3 <= X <= 10) is true for X=4
(3 <= X <= 10) is true for X=10
Brent Bradburn
  • 51,587
  • 17
  • 154
  • 173
2

The only way to do it is the way Marlon suggested. Unfortunately, there's no way to circumvent this with operator overloading. Overloading the operator would look like this: operator<=<=, where <=<= is a ternary operator. The only ternary operator is the "?:", and this would make your syntax look incredibly unsightly. Best to do it the old fashioned way.

rurouniwallace
  • 2,027
  • 6
  • 25
  • 47
2

Since C++11 with its range based for loop, the easiest and simplest is a Range class template that in addition to supporting the loop, supports set membership checking, e.g. as a method contains.

One obvious advantage is that with this approach there’s just one simple template, with multiple usages.

And a perhaps not so obvious advantage is that it supports having a const loop variable, like …

int main()
{
    using std::wcout;  using std::endl;  using cpp::range;

    for( auto const i : range( 2, 7 ) )
    {
        wcout << i << " ";
    }
    wcout << endl;

    if( range( 2, 7 ).contains( 3 ) )
    {
        wcout << "Yes!" << endl;
    }
    else
    {
        wcout << "No!" << endl;
    }
}

Definitions like e.g.

#include <utility>          // std::begin, std::end

namespace cpp {
    template< class Derived >
    class Comparable
    {
    public:
        friend bool operator<=( Derived const& a, Derived const& b )
        { return !(b < a); }

        friend bool operator>=( Derived const& a, Derived const& b )
        { return !(a < b); }

        friend bool operator>( Derived const& a, Derived const& b )
        { return (b < a); }

        friend bool operator!=( Derived const& a, Derived const& b )
        { return !(a == b); }
    };

    template< class TpValue >
    class Range
    {
    public:
        typedef TpValue Value;

    private:
        Value   first_;
        Value   last_;

    public:
        class Iter
            : public Comparable< Iter >
        {
        private:
            Value   current_;

        public:
            Value operator*() const { return current_; }

            void operator++() { ++current_; }

            friend bool operator<( Iter const a, Iter const b )
            { return a.current_ < b.current_; }

            friend bool operator==( Iter const a, Iter const b )
            { return a.current_ == b.current_; }

            explicit Iter( Value const v )
                : current_( v )
            {}
        };

        Value first() const { return first_; }
        Value last() const { return last_; }

        Iter begin() const { return Iter( first_ ); }
        Iter end() const { return Iter( last_ + 1 ); }

        bool contains( Value const x ) const
        { return (first_ <= x && x <= last_); }

        Range( Value const first, Value const last )
            : first_( first ), last_( last )
        {}
    };

    template< class Value >
    Range< Value > range( Value const first, Value const last )
    {
        return Range< Value >( first, last );
    }

    template< class Value >
    typename Range< Value >::Iter begin( Range< Value > const& r )
    { return r.begin(); }

    template< class Value >
    typename Range< Value >::Iter end( Range< Value > const& r )
    { return r.end(); }
}  // namespace cpp

Generalizing this to deal with floating point ranges is left as an exercise for the reader (it's not asked for here).

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
1

First of all: the Standard mandates that operator overloading involve at least one user-defined type in the arguments. Therefore X would have to be of a user-defined type.

If it is, then it becomes possible, no issue.

struct Integral {
    Integral(int i): _value(i) {}
    int _value;
};

class ComparisonResult {
public:
    ComparisonResult(Integral last, bool result): _last(last), _result(result) {}

    operator bool() const { return _result; }

    Integral last() const { return _last; }

private:
    Integral _last;
    bool _result;
};

ComparisonResult operator<(Integral left, integral right) {
    return ComparisonResult(right, left._value < right._value);
}

ComparisonResult operator<(ComparisonResult cr, Integral right) {
     if (not cr) { return ComparisonResult(right, false); }
     return ComparisonResult(right, cr.last() < right);
}

// Other operators here, with the same ComparisonResult type

And then:

int main() {
    Integral X(4);
    if (3 < X < 10) { std::cout << "Hello, World!\n"; }
}
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722