2

I'm playing around with Phoenix v3 trying to figure out whether we should standardize on it instead of the current mix of Bind and Lambda. From the documentation I got the impression that it should be possible to simplify some expressions.

Currently I'm stuck on the usage of the ->* operator in combination with the STL algos. The following will compile (Visual Studio 2008 SP1) but not give the expected output:

#include <algorithm>
#include <iostream>
#include <vector>
#include <boost/mem_fn.hpp>
#include <boost/phoenix.hpp>
using namespace std;
using namespace boost;
using namespace boost::phoenix;
using namespace boost::phoenix::arg_names;

struct A
{
  void f() const { cout << "A"; };
};
struct B
{
  A a() { return A(); };
};

int main()
{
  vector<A> va(1);
  for_each(va.begin(), va.end(), bind(mem_fn(&A::f), arg1));
  for_each(va.begin(), va.end(), arg1->*&A::f);

  vector<B> vb(1);
  for_each(vb.begin(), vb.end(), bind(mem_fn(&A::f), bind(mem_fn(&B::a), arg1)));
  return 0;
}

Running this example will print out 'A' twice, both times for the bind-based loops. So here are my questions:

  • What should I change in order to have the operator-based loop actually call A::f?
  • How could I change the double-bind loop using operators?
  • Anyone know why VS2008 is always complaining when you don't specify mem_fn in these cases? I always get warning C4180 (qualifier applied to function type has no meaning; ignored).

Thanks in advance for any insights.

manlio
  • 18,345
  • 14
  • 76
  • 126
Ben Swerts
  • 78
  • 3

2 Answers2

4

Given:

#include <algorithm>
#include <vector>
#include <iostream>
#include <boost/phoenix.hpp>

struct A
{
    void f() const { std::cout << "A\n"; };
};

struct B
{
    A a() const { return A(); };
};

The first one is an easy fix:

int main()
{
    using boost::phoenix::arg_names::arg1;

    std::vector<A> va(1);
    std::for_each(va.begin(), va.end(), (&arg1->*&A::f)());
}

Regarding operator->*, the Phoenix docs clearly state:

The left hand side of the member pointer operator must be an actor returning a pointer type.

Consequently, when given an object (in this case an A&), one must take the address of said object in order to use operator->* -- hence &arg1. (Also, because Phoenix actors are lazy, one must use an extra set of parenthesis in order to obtain the eager functor rather than the lazy functor.)


The second one is not quite as easy, as one cannot use only operators -- because we must have an actor representing a pointer in order to use operator->*, we would need to take the address of the result of B::a, but the result of B::a is an rvalue and taking the address of any rvalue is illegal. We have two options:

  1. Store the result of B::a into a variable, making it an lvalue, and consequently making it legal to take the address of. This can be accomplished using phoenix::let:

    int main()
    {
        using boost::phoenix::let;
        using boost::phoenix::arg_names::arg1;
        using boost::phoenix::local_names::_a;
    
        std::vector<B> vb(1);
        std::for_each(
            vb.begin(),
            vb.end(),
            (let(_a = (&arg1->*&B::a)())[(&_a->*&A::f)()])
        );
    }
    

    Obviously this is pretty ugly.

  2. Use phoenix::bind instead of operator->*, since it works equally well with references and pointers, obviating the need to take the address of the result of B::a:

    int main()
    {
        using boost::phoenix::bind;
        using boost::phoenix::arg_names::arg1;
    
        std::vector<B> vb(1);
        std::for_each(vb.begin(), vb.end(), bind(&A::f, (&arg1->*&B::a)()));
    }
    
ildjarn
  • 62,044
  • 9
  • 127
  • 211
  • Thanks for the detailed explanation. Looks like I was about to be bitten by the 'features' of VC++ again. Good to know as otherwise our set of test compilers (GCC and ICC) would have had to catch it. Looking back on my question I actually thought the ->* operator could be used as a convenient replacement for bind. Seems I was wrong and I think I'll keep using (phoenix) bind for this kind of stuff. I don't want to worry about iterating over a collection of values or pointers. – Ben Swerts Jul 21 '11 at 20:50
  • +1 for the really elaborate answer. That helped me a lot to understand phoenix better. – mkaes Jul 22 '11 at 07:56
1

I am also not too good at phoenix but I think you cannot use the ->* operator the way you want it to use.
If you change your example to

...
    vector<A*> va;
    va.push_back(new A);
    for_each(va.begin(), va.end(), bind(mem_fn(&A::f), arg1));
    for_each(va.begin(), va.end(), (arg1->*&A::f)());
...

you will get two times A. In the examples I only found examples with pointers so I guess you can only use the phoenix ->* operator with pointers. Which should be ok as the operator ->* binds to pointers.
From the Spec in 5.5:

The binary operator ->* binds its second operand, which shall be of type “pointer to member of T” (where T is a completely-defined class type) to its first operand, which shall be of type “pointer to T” or “pointer to a class of which T is an unambiguous and accessible base class

mkaes
  • 13,781
  • 10
  • 52
  • 72
  • Yes indeed, that works! Now I also know how to do it with values: (&arg1->*&A::f)() – Ben Swerts Jul 21 '11 at 14:52
  • And the double-bind can be replaced by: (&((&arg1->*&B::a)())->*&A::f)(), not that I think it's that much clearer... – Ben Swerts Jul 21 '11 at 14:58
  • @Ben : No, that invokes UB, unless you're compiling with VC++ which allows that as a non-standard compiler extension. I'll post a full answer with more details. – ildjarn Jul 21 '11 at 18:17
  • @ildjarn: Non-standard behavior that breaks conforming programs isn't an extension, it's a **bug**. – Ben Voigt Jul 21 '11 at 18:43
  • @BenVoigt : It doesn't break conforming programs, it allows non-conforming programs to work -- hence extension. – ildjarn Jul 21 '11 at 18:48
  • @ildjarn: This is the "feature" of binding non-const references to temporaries, correct? SO users have encountered all kinds of breakage of conforming programs resulting from that. – Ben Voigt Jul 21 '11 at 19:07
  • @BenVoigt : Indeed, that's the so-called feature. I wasn't aware that it could break conforming code (I've not encountered that yet). – ildjarn Jul 21 '11 at 19:10