In C++11, it is en vogue to use small lambda expressions to produce ad-hoc predicates. However, sometimes it's fun to create predicates in a functional, "point-free" way. That way, no function bodies need to be inspected, the code is mildly self-documenting, and it's a great icebreaker at parties.
Suppose I have a std::vector<std::vector<int>> v
, and I want to remove all the empty inner vectors. The lambda-style remove_if
call might look like this:
std::remove_if(v.begin(),
v.end(),
[](std::vector<int> const & w) -> bool { return w.empty(); });
This is verbose and redundant. The point-free approach uses std::mem_fn
:
std::remove_if(v.begin(),
v.end(),
std::mem_fn(&std::vector<int>::empty));
This works fine. But now the problem: Suppose I want to further compose this predicate, say by negating it. In the lambda this would be a trivial change. But for the functional notation, we would like to use the library function std::not1
to produce a unary_negate
wrapper.
However, the obvious std::not1(std::mem_fn(&std::vector<int>::empty))
does not compile. The problem appears to be that the result type of std::mem_fn
defines its argument_type
member type as a pointer to the class, not a reference; cf. 20.8.10/2:
The simple call wrapper shall define two nested types named
argument_type
andresult_type
as synonyms forcv T*
andRet
, respectively, whenpm
is a pointer to member function with cv-qualifiercv
and taking no arguments, whereRet
is pm’s return type.
This breaks the composability with std::not1
.
I have written this type mangling work-around which strips the unwanted pointer:
#include <type_traits>
template <typename T>
struct result_type_mangler : T
{
using argument_type = typename std::remove_pointer<typename T::argument_type>::type;
result_type_mangler(T t) : T(t) { }
};
template <typename T>
result_type_mangler<T> mangle(T t)
{
return result_type_mangler<T>(t);
}
Now I can compose the predicates:
std::not1(mangle(std::mem_fn(&std::vector<int>::empty)))
Is this a legitimate work-around? Can member function predicates be composed in an easier way? And why is the member function wrapper's argument_type
defined in such a weird way?
2 Answers 2
The verbosity of C++14 generic lambdas should be much lower than std::mem_fun
std::remove_if(v.begin(),
v.end(),
[](auto const & w) { return w.empty(); });
If you want to remove non-empty vectors, you can also do something like
std::function<bool(std::vector<int>)> is_empty = [](auto const & w) { return w.empty(); };
std::remove_if(v.begin(),
v.end(),
std::not1(is_empty));
Oh and the -> bool
in your question is already superfluous in C++11 for single-line lambdas. Generic lambdas are currently supported by Clang>= 3.4, GCC 4.9 and MSVC 2013 November CTP.
I think Scott Meyers even has an Item "Prefer lambdas over bind" in his upcoming book Effective C++11/14.
I think your way works, but there are other solutions:
In C++98, there is actually an std::mem_fun_ref
function.
In C++11, there is the std::ref
and std::cref
functions.
// VS2012 doesn't have uniform initialization, so this is me being lazy
int a1 [] = {1, 2, 3, 4, 5} ;
int a2 [] = {6, 7, 8} ;
std::vector <int> v1 (std::begin (a1), std::end (a1)) ;
std::vector <int> v2 ;
std::vector <int> v3 (std::begin (a2), std::end (a2)) ;
std::vector <std::vector <int> > vv ;
vv.push_back (v1) ;
vv.push_back (v2) ;
vv.push_back (v3) ;
std::cout << vv.size () << std::endl ;
auto end = std::remove_if (std::begin (vv), std::end (vv), std::not1 (std::cref (&std::vector<int>::empty))) ;
vv.erase (end, std::end (vv)) ;
std::cout << vv.size () << std::endl ;
Update
It seems that using std::cref
only works on Visual Studio. std::mem_fun_ref
is a portable but deprecated solution. Therefore, I would say that your way is the best way out of these three solutions.
-
\$\begingroup\$ That sounded interesting, but I can't reproduce that. With gcc,
std::cref(&std::vector<int>::empty)
is an error, e.g. see here. Maybe a VS weirdness? \$\endgroup\$Kerrek SB– Kerrek SB2013年12月23日 17:49:32 +00:00Commented Dec 23, 2013 at 17:49 -
\$\begingroup\$ I looked over the
reference_wrapper
class implementation is Visual Studio 2012. It uses, what I believe is a universal reference in its constructor. I don't own a copy of the standard, but I'm assuming you're correct and that this is VS weirdness.std::mem_fun_ref
seems to work, but that's a deprecated solution. \$\endgroup\$jliv902– jliv9022013年12月23日 18:04:51 +00:00Commented Dec 23, 2013 at 18:04
Explore related questions
See similar questions with these tags.
bind
also doesn't work:std::not1(std::bind(&std::vector<int>::empty, std::placeholders::_1))
\$\endgroup\$