not1/not2 support in C++11

Discussion in 'C++' started by Scott Meyers, Dec 21, 2011.

  1. Scott Meyers

    Scott Meyers Guest

    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
    Scott Meyers, Dec 21, 2011
    #1
    1. Advertising

  2. On 21 déc, 19:11, Scott Meyers <> wrote:
    > 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.

    --
    Michael
    Michael DOUBEZ, Dec 23, 2011
    #2
    1. Advertising

  3. Scott Meyers

    Scott Meyers Guest

    On 12/23/2011 3:41 AM, Michael DOUBEZ wrote:
    > 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
    Scott Meyers, Dec 23, 2011
    #3
  4. Scott Meyers

    Scott Meyers Guest

    On 12/23/2011 5:35 AM, Leigh Johnston wrote:
    > 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
    Scott Meyers, Dec 23, 2011
    #4
  5. On Dec 21, 1:11 pm, Scott Meyers <> wrote:
    > 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
    Howard Hinnant, Dec 24, 2011
    #5
  6. On Dec 24, 3:04 pm, Howard Hinnant <> wrote:
    > 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
    Howard Hinnant, Dec 25, 2011
    #6
  7. Scott Meyers

    Scott Meyers Guest

    On 12/24/2011 12:04 PM, Howard Hinnant wrote:
    > 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
    Scott Meyers, Dec 26, 2011
    #7
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Nick Zdunic
    Replies:
    0
    Views:
    900
    Nick Zdunic
    Nov 5, 2003
  2. John Black

    how to use not1?

    John Black, Jun 15, 2004, in forum: C++
    Replies:
    6
    Views:
    1,467
    Rob Williscroft
    Jun 16, 2004
  3. Steven Knight
    Replies:
    0
    Views:
    1,165
    Steven Knight
    Aug 18, 2004
  4. dragon9

    why not std::not1 ?

    dragon9, Feb 29, 2008, in forum: C++
    Replies:
    1
    Views:
    1,103
    Triple-DES
    Feb 29, 2008
  5. Juha Nieminen
    Replies:
    1
    Views:
    880
    Juha Nieminen
    Jan 27, 2010
Loading...

Share This Page