Topic: defect in bind1st and bind2nd?


Author: John_Maddock@compuserve.com (John Maddock)
Date: 1999/04/15
Raw View
>By changing std::plus I mean changing derivation
>   struct plus : binary_function<T,T,T> // old
>   struct plus : binary_function<const T&, const T&, T> // new
>This is so that first_argument_type matches the first argument of
>operator(), with similar remarks for second_argument_type.

OK I hadn't thought about that, I guess there are argument either way,
at the present time however "it ain't broke" - at least as far as I
know - so as long as we all know what the definition is then I would
say it is probably OK.

BTW going back to what is a "reference to a reference" and the
suggestion that the binders could be fixed by allowing this, consider:

void f1(int, int&);

Binding to f1 could legitamately change the second argument to const
int & in its operator(), but would not then be able to pass the
argument on to the underlying function pointer.

Likewise:

void f2(int, volatile int&)

can not be bound to, either with the present deffinition, or by
allowing references to references - how do you obtain a const T& when
T is a reference to a volatile type?

Sorry to bring this up again :-)

John Maddock
http://ourworld.compuserve.com/homepages/John_Maddock/
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: John_Maddock@compuserve.com (John Maddock)
Date: 1999/04/11
Raw View
>By changing std::plus I mean changing derivation
>   struct plus : binary_function<T,T,T> // old
>   struct plus : binary_function<const T&, const T&, T> // new
>This is so that first_argument_type matches the first argument of
>operator(), with similar remarks for second_argument_type.

OK I hadn't thought about that, I guess there are argument either way,
at the present time however "it ain't broke" - at least as far as I
know - so as long as we all know what the definition is then I would
say it is probably OK.

BTW going back to what is a "reference to a reference" and the
suggestion that the binders could be fixed by allowing this, consider:

void f1(int, int&);

Binding to f1 could legitamately change the second argument to const
int & in its operator(), but would not then be able to pass the
argument on to the underlying function pointer.

Likewise:

void f2(int, volatile int&)

can not be bound to, either with the present deffinition, or by
allowing references to references - how do you obtain a const T& when
T is a reference to a volatile type?

Sorry to bring this up again :-)

John Maddock
http://ourworld.compuserve.com/homepages/John_Maddock/
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: John_Maddock@compuserve.com (John Maddock)
Date: 1999/04/03
Raw View
I'm not sure if this is a defect or intentional design, but neither
bin1st not bin2nd appear to be usable if the function object takes a
reference as one of its parameters - if it does then the binder tries
to create a reference to a reference which is not allowed (as far as I
know)

Any comments?

Thanks in advance,

John Maddock
http://ourworld.compuserve.com/homepages/John_Maddock/
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: John_Maddock@compuserve.com (John Maddock)
Date: 1999/04/04
Raw View
>It's a defect. I have reported it to the committee.
>
>One possible resolution is to consider a T&& equivalent to T&.

Thanks for the prompt reply, I have a related question:  is there a
particular reason why binder1st<>::operator() is declared as const?

It would seem to prohibit the calling of non-const functions
(specifically non-const member functions bound into a function object
with std::mem_fun) and the const modifier does not appear to be
applied to other standard function objects.

BTW I haven't thought much about this, but could the argument by
reference problem be solved with a partial specialisation of bind1st
and bind2nd rather than messing with the language semantics.

Thanks again,


John Maddock
http://ourworld.compuserve.com/homepages/John_Maddock/


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: sbnaran@localhost.localdomain (Siemel Naran)
Date: 1999/04/04
Raw View
On 03 Apr 99 13:37:14 GMT, John Maddock <John_Maddock@compuserve.com> wrote:

>I'm not sure if this is a defect or intentional design, but neither
>bin1st not bin2nd appear to be usable if the function object takes a
>reference as one of its parameters - if it does then the binder tries
>to create a reference to a reference which is not allowed (as far as I
>know)

This issue has come up on this newsgroup and comp.lang.c++.moderated
twice in the last six months.  There is no resolution at present.  Two
possibilities I see are:

(1) Make a reference to a reference mean a reference, at least in
template instantiations (eg, "const const T" means "const T" in the
current rules, so "T & &" should mean "T &").

(2) Change the signature of bind1st's constructor:
       bind1st(const Oper&, const typename Oper::second_argument_type&); // old
       bind1st(const Oper&, typename Oper::second_argument_type); // new

    To make solution (2) work flawlessly, it might be necessary to change
    all the function objects so that the derivation from std::unary_function
    or std::binary_function matches the function object's operator().
       struct plus : public binary_function<T,T,T> {
          T operator()(const T&, const T&) const;
       }; // old
       struct plus : public binary_function<const T&, const T&, T> {
          T operator()(const T&, const T&) const;
       }; // old

--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: John_Maddock@compuserve.com (John Maddock)
Date: 1999/04/05
Raw View
>(2) Change the signature of bind1st's constructor:
>       bind1st(const Oper&, const typename Oper::second_argument_type&); // old
>       bind1st(const Oper&, typename Oper::second_argument_type); // new
>
>    To make solution (2) work flawlessly, it might be necessary to change
>    all the function objects so that the derivation from std::unary_function
>    or std::binary_function matches the function object's operator().
>       struct plus : public binary_function<T,T,T> {
>          T operator()(const T&, const T&) const;
>       }; // old
>       struct plus : public binary_function<const T&, const T&, T> {
>          T operator()(const T&, const T&) const;
>       }; // old

Having looked some more at this, your suggestion (2) seems the best,
note that it is necessary to change both the constructor and
operator() for each of the binderXXX objects take arguments by value.
This behaviour is consistant with both the ptr_fun and mem_fun objects
which already take argument by value.
The problem with references of references is that there are a large
number of possiblities: with either the reference or the referenced
object being const or not, or volatile or not, in the general case.
It also entails a compiler change rather than a "simple" standard
library patch - potentially one which users could apply/create for
themselves if they can't wait for their compiler vendor to catch up.

Finally if you mix mem_fun with binders as in :

std::bind1st(std::mem_fun(&ClassName::Method), ObjectPointer);

then the compiler generates a "call of non-const function for const
object" error/warning.  The problem is that operator() for the binder
family is const, where as it is non-const for the mem_fun family, at a
guess the mem_fun family needs to be the one to change - provided that
does not prohibit the calling of non-const member functions.


John Maddock
http://ourworld.compuserve.com/homepages/John_Maddock/


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: "AllanW {formerly AllanW@my-dejanews.com}" <allan_w@my-dejanews.com>
Date: 1999/04/08
Raw View
In article <370740b9.2112119@news.freeserve.net>,
  John_Maddock@compuserve.com (John Maddock) wrote:
> is there a
> particular reason why binder1st<>::operator() is declared as const?
> It would seem to prohibit the calling of non-const functions
> (specifically non-const member functions bound into a function object
> with std::mem_fun) and the const modifier does not appear to be
> applied to other standard function objects.

When a function (or operator()) is const, then the implementation
of that function is prohibited from changing the state of that
object. In this case, binder1st<>::operator() is const, which
prohibits binder() from changing the state of the binder.

This does not mean that the function called by operator() must be
const. They don't even have to be member functions.

----
Allan_W@my-dejanews.com is a "Spam Magnet" -- never read.
Please reply in USENET only, sorry.

-----------== Posted via Deja News, The Discussion Network ==----------
http://www.dejanews.com/       Search, Read, Discuss, or Start Your Own
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: sbnaran@localhost.localdomain (Siemel Naran)
Date: 1999/04/08
Raw View
On 5 Apr 1999 23:01:36 GMT, Andrei Alexandrescu <andrewalex@hotmail.com> wrote:

>Now instead of using 'const Pred::first_argument_type &' inside
>bind1st, one would use
>'type_traits<Pred::first_argument_type>::const_reference'. That type
>will always map correctly.

A solution number three!  I didn't think of this.  But one must say:
 'typename type_traits<typename Pred::first_argument_type>::const_reference'

--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: John_Maddock@compuserve.com (John Maddock)
Date: 1999/04/08
Raw View
>>Having looked some more at this, your suggestion (2) seems the best,
>>note that it is necessary to change both the constructor and
>>operator() for each of the binderXXX objects take arguments by value.

Perhaps I should have been more explicit about that, by value I mean
that the template argument T is passed by value - T of course could be
a pointer or reference type.

Like you I don't think that effeciency is an issue - the only place
where a possible ineffeciency could occur by making this change is if
the function takes a struct or class by value - and in that case the
inefficency arises either because of the users code, not explicitly
because of the library design. In any case as you say the code should
be inlined out of existance in any case if the compiler is doing its
job.

>In the current rules, a reference to a reference is illegal.  As a
>result, people write their code in such a way that a reference to
>a reference does not occur.  But if we make a reference to a
>reference legal, then people are no longer constrained in the way
>they write code.  The question is whether this constraint is a good
>thing or a silly restriction.

It seems logical that it should be illegal (at least to me), maybe
thats why I'm less keen on a change.

>OK, but changing the library is still quite a big thing.  People
>are likely to complain that pass by value is inefficient, whereas
>I've showed that it is not (at least in a good compiler).  And what
>about chaning std::plus and so on?

std::plus is not generally instanciated for pointer of reference types
so passing by reference is sensible here.

>What are you talking about?  The mem_fun family's operator() is a
>const member function.

I don't have the lastest full standard, in the draft it is non-const:

  20.3.8  Adaptors for pointers to
[lib.member.pointer.adaptors]
       members
1 The purpose of the following is to provide  the  same  facilities
for
  pointer  to  members  as  those  provided for pointers to functions
in
  _lib.function.pointer.adaptors_.
    template <class S, class T> class mem_fun_t
          : public unary_function<T*, S>
{
    public:
      explicit mem_fun_t(S (T::*p)());
      S operator()(T* p);
  };

If this has already changed, let me know and I'll submit a bug report
to my library vendor.

John Maddock
http://ourworld.compuserve.com/homepages/John_Maddock/


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: sbnaran@fermi.ceg.uiuc.edu (Siemel Naran)
Date: 1999/04/08
Raw View
On 8 Apr 1999 15:00:33 GMT, John Maddock <John_Maddock@compuserve.com> wrote:

>>OK, but changing the library is still quite a big thing.  People
>>are likely to complain that pass by value is inefficient, whereas
>>I've showed that it is not (at least in a good compiler).  And what
>>about chaning std::plus and so on?

By changing std::plus I mean changing derivation
   struct plus : binary_function<T,T,T> // old
   struct plus : binary_function<const T&, const T&, T> // new
This is so that first_argument_type matches the first argument of
operator(), with similar remarks for second_argument_type.

>std::plus is not generally instanciated for pointer of reference types
>so passing by reference is sensible here.


>  _lib.function.pointer.adaptors_.
>    template <class S, class T> class mem_fun_t
>          : public unary_function<T*, S>
>{
>    public:
>      explicit mem_fun_t(S (T::*p)());
>      S operator()(T* p);
>  };
>
>If this has already changed, let me know and I'll submit a bug report
>to my library vendor.

Not right.  Operator() should be a const member function.

--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: sbnaran@bardeen.ceg.uiuc.edu (Siemel Naran)
Date: 1999/04/05
Raw View
On 5 Apr 1999 14:46:42 GMT, John Maddock <John_Maddock@compuserve.com> wrote:
>Siemel Naran

>>(2) Change the signature of bind1st's constructor:
>>     bind1st(const Oper&, const typename Oper::second_argument_type&); // old
>>     bind1st(const Oper&, typename Oper::second_argument_type); // new

                                           ^^^^^^ should be "first"


>Having looked some more at this, your suggestion (2) seems the best,
>note that it is necessary to change both the constructor and
>operator() for each of the binderXXX objects take arguments by value.

Small point.  The phrase "take by value" is misleading.  For as
'second_argument_type' may be a reference type, the version
   bind1st(const Oper&, typename Oper::first_argument_type); // new
may actually be taking the second argument by reference!

In the case where 'first_argument_type' is actually a value type,
then pass by reference to const is just as efficient as pass by value!
Consider:

   template <class Oper>
   struct binder1st {
      public:
         bind1st(const Oper&, const typename Oper::first_argument_type& arg1)
           : oper(oper), arg1(arg1) { }

         typename Oper::result_type
         operator()(const typename Oper::second_argument_type& arg2) const
            { return oper(arg2); }

      private:
         Oper oper;
         typename Oper::first_argument_type arg1;
   };

Let's suppose that Oper::first_argument_type==MyClass, where MyClass
is a user class (and not a typedef to a reference type).
   class MyClass { ... };

Now consider:
   binder1st<Oper> b(o,m);
If 'm' passed by reference to const, then
   [X] 0 calls to MyClass's copy ctor for passing 'm'
   [X] 1 call  to MyClass's copy ctor for copying 'arg1' into 'binder1st::arg1'
If 'm' passed by value, then
   [X] 1 call  to MyClass's copy ctor for passing 'm'
   [X] 1 call  to MyClass's copy ctor for copying 'arg1' into 'binder1st::arg1'
But because the constructor is inline, a good compiler will elide one
of the two copies, and copy the original 'm' directly into 'binder1st::arg1'.
So the "pass by value" (which may actually be pass by reference) is
still maximally efficient!



>This behaviour is consistant with both the ptr_fun and mem_fun objects
>which already take argument by value.



>The problem with references of references is that there are a large
>number of possiblities: with either the reference or the referenced
>object being const or not, or volatile or not, in the general case.

I don't think this is a problem.  Because "T const & const &" would
be the same as "T const & &", which is the same as "T const &".

In the current rules, a reference to a reference is illegal.  As a
result, people write their code in such a way that a reference to
a reference does not occur.  But if we make a reference to a
reference legal, then people are no longer constrained in the way
they write code.  The question is whether this constraint is a good
thing or a silly restriction.


>It also entails a compiler change rather than a "simple" standard
>library patch - potentially one which users could apply/create for
>themselves if they can't wait for their compiler vendor to catch up.

OK, but changing the library is still quite a big thing.  People
are likely to complain that pass by value is inefficient, whereas
I've showed that it is not (at least in a good compiler).  And what
about chaning std::plus and so on?

>>    To make solution (2) work flawlessly, it might be necessary to change
>>    all the function objects so that the derivation from std::unary_function
>>    or std::binary_function matches the function object's operator().
>>       struct plus : public binary_function<T,T,T> {
>>          T operator()(const T&, const T&) const;
>>       }; // old
>>       struct plus : public binary_function<const T&, const T&, T> {
>>          T operator()(const T&, const T&) const;
>>       }; // old



>Finally if you mix mem_fun with binders as in :
>
>std::bind1st(std::mem_fun(&ClassName::Method), ObjectPointer);
>
>then the compiler generates a "call of non-const function for const
>object" error/warning.  The problem is that operator() for the binder
>family is const, where as it is non-const for the mem_fun family, at a
>guess the mem_fun family needs to be the one to change - provided that
>does not prohibit the calling of non-const member functions.

What are you talking about?  The mem_fun family's operator() is a
const member function.

--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: "Andrei Alexandrescu" <andrewalex@hotmail.com>
Date: 1999/04/05
Raw View
Bjarne Stroustrup wrote in message ...
>John_Maddock@compuserve.com (John Maddock) writes:
>It's a defect. I have reported it to the committee.
>
>One possible resolution is to consider a T&& equivalent to T&.

One other possible solution - that wouldn't incur a language change -
is to use a traits mechanism:

template <typename T>
struct type_traits
{
    typedef T & reference;
    typedef const T & const_reference;
    //...
};

template <typename T>
struct type_traits<T &>
{
    typedef T & reference;
    typedef const T & const_reference;
    //...
};

Now instead of using 'const Pred::first_argument_type &' inside
bind1st, one would use
'type_traits<Pred::first_argument_type>::const_reference'. That type
will always map correctly.

Andrei




[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]