Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pretty way to say "foo not in {bar, baz}" in C++11/14

Tags:

c++

c++11

c++14

I'm writing C++ and missing the clarity of Python. But I know that C++ has been evolving and wondering if there's a nicer way to do something like this:

if (foo != bar && foo != baz)

In Python I would do this:

if foo not in {bar, baz}:

Is there a fancy feature in C++11 or C++14 that allows me to do something similarly readable?

Edit: A lot of people are wondering why I'm trying to replace something so short. I'm not, but I didn't want to make my example as ugly and unreadable as the original code. It's more like:

if (somelongvariablename.somelongmethodname() !=
    SomeReallyLongNamespace::AndAnotherSubClassname::A_LONG_CONSTANT_NAME &&
    somelongvariablename.somelongmethodname() !=
    SomeReallyLongNamespace::AndAnotherSubClassname::ANOTHER_LONG_CONSTANT_NAME) {
    // foo
like image 504
guidoism Avatar asked Jun 17 '16 17:06

guidoism


2 Answers

How about something like this:

#include <type_traits>
#include <tuple>
#include <utility>

template <typename ...Args> struct InT: std::tuple<Args...>
{
    template <typename ...Brgs>
    explicit InT(Brgs &&... brgs)
    : std::tuple<Args...>(std::forward<Brgs>(brgs)...) {}

    template <typename T, std::size_t ...I>
    bool noteq(T && t, std::index_sequence<I...>) const
    {
        return (true && ... && (t != std::get<I>(*this)));
    }
};

template <typename ...Args>
InT<Args &&...> AnyOf(Args &&... args)
{
    return InT<Args &&...>(std::forward<Args>(args)...);
}

template <typename T, typename ...Args>
bool operator!=(T && t, InT<Args...> in)
{
    return in.noteq(std::forward<T>(t), std::index_sequence_for<Args...>());
}

Usage:

if (x != AnyOf(1, 3, 5)) { f(); }
like image 75
Kerrek SB Avatar answered Oct 15 '22 20:10

Kerrek SB


We can get this syntax:

int main() {
  if (foo *in* std::tie(bar, baz)) {
  }
}

live example.

It also works with C arrays or std containers or the like on the right hand side of *in*.

This should be zero overhead after the optimizer gets its teeth into it.

Negating is just:

  if (!(foo *in* std::tie(bar, baz)))

as I don't think a special case is a good plan. If you want the foo *not in* std::tie(bar, baz)) syntax, see bottom of this post.


First, a named operator library:

namespace named_operator {
  template<class D>struct make_operator{constexpr make_operator(){}};

  template<class T, char, class O> struct half_apply { T&& lhs; };

  template<class Lhs, class Op>
  half_apply<Lhs, '*', Op> operator*( Lhs&& lhs, make_operator<Op> ) {
    return {std::forward<Lhs>(lhs)};
  }

  template<class Lhs, class Op, class Rhs>
  auto operator*( half_apply<Lhs, '*', Op>&& lhs, Rhs&& rhs )
  -> decltype( invoke( std::declval<Lhs>(), Op{}, std::declval<Rhs>() ) )
  {
    return invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) );
  }
}

which is about 12 lines long, and makes named operators easy.

Now we make a named operator. namespace my_ns { constexpr struct in_tag:named_operator::make_operator {} in {}; } using my_ns::in; It needs an action. The C++17 version is easy:

namespace my_ns {
  // foo in tuple support:
  template<class T, class...Args>
  bool invoke( T const& lhs, in_tag, std::tuple<Args...> const& rhs ) {
    return std::apply( [&](auto&&...args){
      return (false || ... || (lhs == args));
    }, rhs);
  }
  // foo in container support:
  template<class T, class Container>
  bool invoke( T const& lhs, in_tag, Container const& rhs ) {
    using std::begin; using std::end;
    auto it = std::find( begin(rhs), end(rhs), lhs );
    return it != end(rhs);
  }
}

The C++11 tuple support version is a bit trickier, because of the lack of std::apply and fold expansion:

namespace my_ns {
  // tuple support:
  template<class T, class...Args, std::size_t...Is>
  bool invoke( T const& lhs, in_tag, std::tuple<Args...> const& rhs, std::index_sequence<Is...> ) {
    bool retval = false;
    using discard=int[];
    (void)discard{ 0,(void(
      retval = retval || (lhs == std::get<Is>(rhs))
    ),0)... };
    return retval;
  }
  template<class T, class...Args>
  bool invoke( T const& lhs, in_tag, std::tuple<Args...> const& rhs ) {
    return invoke(lhs, in_tag{}, rhs, std::index_sequence_for<Args...>{} );
  }
  // container support is identical to C++17 version
}

As mentioned above, if you want

  if (foo *not in* std::tie(bar, baz))

I can do it.

Add a not_in operator:

namespace my_ns {
  constexpr struct not_in_tag:named_operator::make_operator<not_in_tag> {} not_in {};
}
using my_ns::not_in;

We then define ! that toggles between them:

namespace my_ns {
  constexpr not_in_tag operator!(in_tag){return {};}
  constexpr in_tag operator!(not_in_tag){return {};}
|

and what the not_in operator does:

namespace my_ns {
  template<class T, class Rhs>
  bool invoke( T const& lhs, not_in_tag, Rhs const& rhs ) {
    return !invoke(lhs, in_tag{}, rhs );
  }
}

for invoke.

Now we get

  if (foo *not in* std::tie(bar, baz)) {
    std::cout << "foo not in {bar,baz}\n";
  }
  if (foo *not in* std::make_tuple(bar, baz, 3)) {
    std::cout << "foo not in {bar,baz, 3}\n";
  }

or

  if (foo *not_in* std::tie(bar, baz)) {
    std::cout << "foo not in {bar,baz}\n";
  }

or

  if (foo *!in* std::tie(bar, baz)) {
    std::cout << "foo not in {bar,baz}\n";
  }

whichever you want.

live example

like image 4
Yakk - Adam Nevraumont Avatar answered Oct 15 '22 20:10

Yakk - Adam Nevraumont