Jan 232015
 

Sean Parent posted today via Twitter this small neat algorithm:

template <class F, class... Args>
void for_each_argument(F f, Args&&... args) {
    [](...){}((f(std::forward<Args>(args)), 0)...);
}

 

 

Problem:

During my today’s work I came across the problem that inside a predicate there was a check if a specific enum value is equal to a set of possible values.

So the code looked like this:

auto predicate = [](State state) {
  return state == MyEnumClass::ValueA || 
         state == MyEnumClass::ValueD || 
         state == MyEnumClass::ValueEWithAVeryLongName; 
};

I don’t like this collection of comparisons for equalness. From my point of view it is some kind of semantically copy-and-paste.

So with Sean’s code in mind I started thinking and I searched for alternatives. The first used a std::initializer_list:

template <typename T>
bool is_in(const T& val, const std::initializer_list<T>& values) {
  return std::find(values.cbegin(), values.cend(), val) != values.cend();
}

So the example from above would read like this:

auto predicate = [](State state) { 
  return is_in(state, { MyEnumClass::ValueA, 
                        MyEnumClass::ValueD, 
                        MyEnumClass::ValueEWithAVeryLongName} ); 
};

That is already better, but it does not read so fluently. So I tried to solve this with a builder like pattern:

template <class T>
struct Is {
  const T d_;
  bool in(const std::initializer_list<T>& values) {
    return std::find(values.cbegin(), values.cend(), d_) != values.cend();
  }
};

template <class T>
Is<T> is(T d) { 
  return Is<T>{d}; 
}

The code example would then be like this:

auto predicate = [](State state) { 
  return is(state).in( { MyEnumClass::ValueA, 
                         MyEnumClass::ValueD, 
                         MyEnumClass::ValueEWithAVeryLongName } ); 
};

That’s much better, but the curly braces inside the parameter are annoying. It would read much better this way and would be less typing, don’t you think?

auto predicate = [](State state) { 
  return is(state).in(MyEnumClass::ValueA, 
                      MyEnumClass::ValueD, 
                      MyEnumClass::ValueEWithAVeryLongName); 
};

So this is my next attempt to solve it with variadic templates:

template <class T> 
struct Is 
{ 
  T d_; 
  bool in(T a) { 
    return a == d_; 
  } 
  template <class Arg, class... Args> 
  bool in(Arg a, Args... args) { 
    return in(a) || in(args...); 
  } 
}; 

template <class T> 
Is<T> is(T d) { 
  return Is<T>{d}; 
}

My next idea was, that there must be a way to do it with variadic templates without the recursion terminating bool in(T)  method.

 

So I played around a bit with Sean’s code, after I understood what it is actually doing, this is the result (thanks to cpplearner for the improvement hint):

struct Is {
 const T d_;
 
 template <class... Args>;
 bool in(Args... args) {
   bool r{ false };
   [](...){}(( (r = r || d_ == args), 1)...);
   return r;
 }
};

template <class T>;
Is<lt> is(T d) {
 return Is<T>{d};
}

One disadvantage of this solution is, that the order of evaluation of the arguments is not defined.

But with Jay Miller’s comment, based on Eric Niebler’s improved version of the for_each_arg, this keeps the order of evaluation of the arguments.

template <class T>
struct Is {
  const T d_;
  template <class... Args>
  bool in(Args... args) {
    bool r {false};
    return (void)std::initializer_list<int>{ (r = r || d_ == args)... },r;
  }
};

template <class T>
Is<T> is(T d) { 
  return Is<T>{d}; 
}

Conclusion:

The last version is the most compact one, but it has the disadvantage compared to all the others, that the internal comparisons does not terminate early. So the code will compare all possible values, even a match was already found. As long as the value to be checked against the set of values cannot be evaluated during compile time, I don’t see a way to do it in a similar way like the last version. If you see one, please let me know.

Unfortunately the code does not fulfill Sean’s challenge: “Goal for Better Code: Fit each function in a tweet”. Never the less I think it is worth sharing it.

Since this is almost my very first experiment with variadic templates, all ideas for improvements or comments are welcome.

 

Note:

Please use “crayon” to enter source code in he comment. The “code” tag removes all “<” and “>”.

This code does not compile with Visual Studio 2013 update 3 in release mode, only in debug. It works fine with update 4 and VS2015 CTP and the recent Clang and GCC.

 

  8 Responses to “A Small Set Algorithm for Enum Values”

  1. Nice, this is my old C++03 solution to the same problem
    Syntax: if (equal_any(7) << 3 << 6) {...}

     namespace _impl {
      template 
      struct equal_any_impl {
        equal_any_impl(const ValueType& ref) : ref_(ref), status_(false) {}
        equal_any_impl& operator<<(const ValueType& test) {
          if (test == ref_)
            status_ = true;
          return *this;
        }
        operator bool() const {
          return status_;
        }
      private:
        bool status_;
        const ValueType& ref_;
      };
    }
    template 
    _impl::equal_any_impl equal_any(const ValueType& ref) {
      return _impl::equal_any_impl(ref);
    }
    
    
    			
  2. Based on Eric Niebler’s follow up to this same tweet, this version guarantees order of evaluation:

    template <class T>
    struct Is {
      const T d_;
      template <class... Args>
      bool in(Args... args) {
        bool r{ false };·
        return (void)std::initializer_list<int>{ (r = r || d_ == args)... },r;
      } 
    };
    
    • Yea. I wrote this before the final version of Eric was available. Thanks for the hint. I will update it.

  3. For that matter, in the original Sean Parent post, the reason for the comma operation in the pack expansion is because the values being unpacked are function calls that may return void. Yours has a real value so that comma is unnecessary:

    template 
    struct Is {
      const T d_;
      template 
      bool in(Args... args) {
        bool r{ false };·
        [&r](...){}(( r = r || d_ == args)...);
        return r;
      }
    };
    
  4. > [&r](...){}(( (r = r || d_ == args), 1)...);
    

    The `&r` is not necessary: the lambda expression, namely `[&r](…){}`, doesn’t need to know what `r` is. Only the computation of its arguments uses the variable `r`.

    • You are right. I oversaw this. Will correct it.
      Many thanks!

  5. template 
    struct Is {
    	const T d_;
    
    	template 
    	bool in(Args... args) const {
    		std::initializer_list ps{ (d_ == args)... };
    		return std::any_of(
    				std::begin(ps),
    				std::end(ps),
    				[](bool p){return p;});
    	}
    };
    

    Calling any_of provides early termination.

Sorry, the comment form is closed at this time.