8
\$\begingroup\$

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 and result_type as synonyms for cv T* and Ret, respectively, when pm is a pointer to member function with cv-qualifier cv and taking no arguments, where Ret 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?

\$\endgroup\$
  • \$\begingroup\$ Note: bind also doesn't work: std::not1(std::bind(&std::vector<int>::empty, std::placeholders::_1)) \$\endgroup\$ – Kerrek SB Dec 23 '13 at 17:53
  • \$\begingroup\$ Those parties sound fun! \$\endgroup\$ – TemplateRex Dec 29 '13 at 8:58
3
\$\begingroup\$

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.

\$\endgroup\$
2
\$\begingroup\$

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.

\$\endgroup\$
  • \$\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 Dec 23 '13 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 Dec 23 '13 at 18:04

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.