not1/not2 support in C++11

S

Scott Meyers

Consider the following program, which was sent to me by a colleague last
summer along with a question as to whether not1 and not2 were supposed
to work with bind:

#include <algorithm>
#include <array>
#include <functional>
#include <iostream>
using namespace std;
using namespace std::placeholders;

template<class FIter, class Fun, class Pred>
void for_each_if(FIter beg, FIter end, Fun f, Pred pred) {
while (beg != end) {
if (pred(*beg))
f(*beg);
++beg;
}
}

void f(int n) {
cout << n << "\n";
}

int main() {
array<int,4> a = {1,2,3,4};
for_each_if(a.begin(), a.end(), f, not1(bind2nd(modulus<int>(),2)));
// uses bind2nd and compiles
for_each_if(a.begin(), a.end(), f, not1(bind(modulus<int>(),_1,2)));
// uses bind and doesn't compile
}

The problem is that std::bind doesn't declare the typedefs that
std::not1 expects, notably argument_type. 20.8/2 says that std::bind's
return type is unspecified, but 20.8.9.1.2/3 says that it's a call
wrapper, and 20.8.1/3-6 say that call wrappers are function objects, and
20.8/5 says function objects taking 1 or 2 arguments have to offer
typedefs like argument_type. All this leads me to believe that not1 and
not2 should work with objects returned from std::bind (and also to
std::function objects) as long as they have arity 1 or 2. Is this valid
reasoning?

Thanks,

Scott
 
M

Michael DOUBEZ

Consider the following program, which was sent to me by a colleague last
summer along with a question as to whether not1 and not2 were supposed
to work with bind:

#include <algorithm>
#include <array>
#include <functional>
#include <iostream>
using namespace std;
using namespace std::placeholders;

template<class FIter, class Fun, class Pred>
void for_each_if(FIter beg, FIter end, Fun f, Pred pred) {
   while (beg != end) {
     if (pred(*beg))
       f(*beg);
     ++beg;
   }

}

void f(int n) {
   cout << n << "\n";

}

int main() {
   array<int,4> a = {1,2,3,4};
   for_each_if(a.begin(), a.end(), f, not1(bind2nd(modulus<int>(),2)));
  // uses bind2nd and compiles
   for_each_if(a.begin(), a.end(), f, not1(bind(modulus<int>(),_1,2)));
  // uses bind and doesn't compile

}

The problem is that std::bind doesn't declare the typedefs that
std::not1 expects, notably argument_type.  20.8/2 says that std::bind's
return type is unspecified, but 20.8.9.1.2/3 says that it's a call
wrapper, and 20.8.1/3-6 say that call wrappers are function objects, and
20.8/5 says function objects taking 1 or 2 arguments have to offer
typedefs like argument_type.  All this leads me to believe that not1 and
not2 should work with objects returned from std::bind (and also to
std::function objects) as long as they have arity 1 or 2.  Is this valid
reasoning?

See
http://drdobbs.com/184401949
[...]
Again, there are cases where TR1-style call wrapper types cannot
define a nested type that gives the return type of their function call
operators. Since Standard Library-style call wrappers depend on
result_type to determine the return type of their function call
operators, this sometimes means that you can't use a TR1-style call
wrapper type as the template argument to a Standard Library call
wrapper type. [...]

In your case, this may be a quality of implementation issue because
bind should be able to determine the argument/return types.

As for the wording of the standard, this may be a defect; IIRC the
return value of bind() is supposed to be unspecified.
 
S

Scott Meyers

See
http://drdobbs.com/184401949
[...]
Again, there are cases where TR1-style call wrapper types cannot
define a nested type that gives the return type of their function call
operators. Since Standard Library-style call wrappers depend on
result_type to determine the return type of their function call
operators, this sometimes means that you can't use a TR1-style call
wrapper type as the template argument to a Standard Library call
wrapper type. [...]

That information applies only to TR1, which was dependent on C++03. I'm
asking about C++11, which includes new facilities, including decltype.
Given decltype, I believe it is always possible for the call wrapper
returned by std::bind to statically compute result_type.
In your case, this may be a quality of implementation issue because
bind should be able to determine the argument/return types.

It's not a QOI issue if the standard requires it, and the reasoning I
posted suggests to me that it is.
As for the wording of the standard, this may be a defect; IIRC the
return value of bind() is supposed to be unspecified.

The return type is unspecified, but it's defined to be a call wrapper,
and the standard imposes requirements on call wrappers. It's similar to
the situation with lambda expressions, where the type of the created
function object is unspecified, but there are still some guarantees
about it we can depend on.

Scott
 
S

Scott Meyers

Actually just because unary_function and binary_function are deprecated
doesn't mean that the typedefs are also deprecated as the typedefs are
now (in C++11) directly contained in the standard functors themselves
(such as std::less) which are no longer derived from
unary_function/binary_function.

Right. At least that's my understanding.

Scott
 
H

Howard Hinnant

Consider the following program, which was sent to me by a colleague last
summer along with a question as to whether not1 and not2 were supposed
to work with bind:

#include <algorithm>
#include <array>
#include <functional>
#include <iostream>
using namespace std;
using namespace std::placeholders;

template<class FIter, class Fun, class Pred>
void for_each_if(FIter beg, FIter end, Fun f, Pred pred) {
   while (beg != end) {
     if (pred(*beg))
       f(*beg);
     ++beg;
   }

}

void f(int n) {
   cout << n << "\n";

}

int main() {
   array<int,4> a = {1,2,3,4};
   for_each_if(a.begin(), a.end(), f, not1(bind2nd(modulus<int>(),2)));
  // uses bind2nd and compiles
   for_each_if(a.begin(), a.end(), f, not1(bind(modulus<int>(),_1,2)));
  // uses bind and doesn't compile

}

The problem is that std::bind doesn't declare the typedefs that
std::not1 expects, notably argument_type.  20.8/2 says that std::bind's
return type is unspecified, but 20.8.9.1.2/3 says that it's a call
wrapper, and 20.8.1/3-6 say that call wrappers are function objects, and
20.8/5 says function objects taking 1 or 2 arguments have to offer
typedefs like argument_type.  All this leads me to believe that not1 and
not2 should work with objects returned from std::bind (and also to
std::function objects) as long as they have arity 1 or 2.  Is this valid
reasoning?

I could be wrong here, bind is quite a complex machine, but I don't
think this is going to work for any known bind implementation. Nor do
I believe the committee intended that it work. The bind functor is
specified to have the typedef result_type, at least some of the time,
as bind is specified ([func.bind.bind]/p3) to have a weak result type
(which is subsequently specified in [func.require]/p3). But the
argument typedefs are more problematic.

Note that the functor returned by bind(modulus<int>(),_1,2) isn't just
a unary functor. It can be called with:

bind(modulus<int>(),_1,2)(3, 4) // equivalent to modulus<int>()
(3, 2)

making it act like a binary functor. So one client may believe that
bind(modulus<int>(),_1,2) should have argument_type, whereas another
client may be expecting first_argument_type and second_argument_type.
It is difficult for bind to figure out at compile time exactly what
the arguments will be at bind-object construction time.

boost::bind solves this problem by providing a operator!() on bind:

   for_each_if(a.begin(), a.end(), f, !bind(modulus<int>(),_1,2));

This idea was considered and rejected as too late for C++11.

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3224.html#gb97

Another workaround is (I believe) to use lambdas:

for_each_if(a.begin(), a.end(), f, [](int x){return !
bind(modulus<int>(),x,2)();});

boost's operator!() is a lot nicer (imho).

One could also write a generalized not functor fairly easily as in
this stack overflow question:

http://stackoverflow.com/q/5567342/576911

for_each_if(a.begin(), a.end(), f,
not_(bind(modulus<int>(),_1,2)));

which approaches the syntactic nicety of operator!() for bind.

Howard
 
H

Howard Hinnant

I could be wrong here, bind is quite a complex machine, but I don't
think this is going to work for any known bind implementation.  Nor do
I believe the committee intended that it work.

I was thinking that maybe I could implement this, if I were just
clever enough. But I'm beginning to think it is unimplementable.
Consider this case:

#include <functional>
#include <cassert>

using namespace std;
using namespace std::placeholders;

long long
g(char a, int b, unsigned c)
{
return a + b + c;
}

int main()
{
auto p = bind(g, _2, _2, _2);
assert(p() == 9); // #1
assert(p(5.5) == 9); // #2
assert(p(5.5, 3ull) == 9); // #3
assert(p(5.5, 3ull, 'a') == 9); // #4
assert(p(5.5, 3ull, 'a', 6u) == 9); // #5
}

#1 and #2 do not compile. So let's not worry about those. But this
implies that that decltype(p) is not a unary_function, and thus
doesn't have an argument_type type.

#3 compiles. It is a binary_function. What is first_argument_type?
It could be anything! It is completely ignored. char is no more
appropriate than int, which is no more appropriate than class X! (you
can read that as an exclamation point or a factorial operator :)).
second_argument_type must be convertible to all of char, int and
unsigned. But which, if any, of those three types should
second_argument_type be?

#4 and #5 also compile and work correctly. But they aren't unary or
binary functors, and yet the decltype(p) is the same type.

*Maybe* a sufficiently smart bind could not define first_argument_type
in this case, or maybe set it to void. And *maybe* it could set
second_argument_type to common_type<char, int, unsigned>::type. But
would this actually be useful? The rules get so complicated that it
becomes difficult for clients to have an obvious and expected outcome
of bind.

*Maybe* the design of argument_type, first_argument_type,
second_argument_type, not1 and not2 are simply obsolete with respect
to bind. boost::bind has moved beyond these needs/ideas for years
now.

Howard
 
S

Scott Meyers

One could also write a generalized not functor fairly easily as in
this stack overflow question:

http://stackoverflow.com/q/5567342/576911

for_each_if(a.begin(), a.end(), f,
not_(bind(modulus<int>(),_1,2)));

which approaches the syntactic nicety of operator!() for bind.

I personally like this solution, because it solves the problem for more
than just the function objects that std::bind produces, but given that
Boost supports operator! for boost::bind-produced objects, adding that
requirement to std::bind-produced objects seems reasonable to me.

My current impression is that not1 and not2 should probably have been
deprecated along with bind1st and bind2nd.

I think that the standard's wording regarding the constraints on
std::bind-produced objects and the availability of argument- and
result-type typedefs should probably be revised, because I think that
the line of reasoning I originally posted is valid. Thanks to your very
thorough discussion of the implementation difficulties, however, it
seems apparent that the best solution is to make it clear that objects
produced by std::bind are never guaranteed to offer the typedefs that
not1/not2 require.

Regarding the implementation of class negation in the StackOverflow
article you referred to, I was surprised to see no use of std::forward
on the arguments to operator() when forwarding them. I think this may
be a common oversight for people writing forwarding functions: they
remember to write a template, they remember to use "&&" parameters, they
remember to use variadic templates, but they forget to apply
std::forward to the arguments they forward. I've seen it more than
once. Funny. Either that or I'm funny for thinking that they need to
use std::forward in such contexts....

Thanks for your very detailed posts, and thanks especially for taking
the time right before Christmas.

Scott
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,011
Latest member
AjaUqq1950

Latest Threads

Top