
Hey everyone, This e-mail is going to be a case study of my personal experience in converting code using Boost.Local to use Boost.Phoenix instead in order to get insight into the similarities and differences in what the libraries have to offer. I do not claim to have perfect understanding of Boost.Phoenix so I acknowledge that it is entirely possible that any negative experience I may have are due to my ignorance rather than a fault in the library itself. To give you some background of where I am coming from, I am a computational scientist working on a code that will let me prove some properties of a space of objects by exhaustive search. Fortunately it turns out that there are redundancies within this space so that I only need to search a subset of it. Thus, what I am working on now is a code which uses the Gecode library to generate solutions within this space that conform to constraints, where the constraints are chosen to only filter out redundant elements of the space. A significant part of this code is a suite of tests to ensure that my constraints work correctly, and it is this portion of the code that makes extensive use of Boost.Local and which I have attempted to convert to use Boost.Phoenix. First, it would have been helpful if I had known that Boost.Phoenix requires version 1.46 of Boost.Proto and Boost.Fusion, since I naively downloaded the sources to Boost.Phoenix into my local repository and discovered (after wading through pages of error messages) that it was missing a header. Since I have version 1.45 installed I concluded it must rely on sources from the trunk so I downloaded Boost.Proto, and then pages of error message later Boost.Fusion. (Incidentally, this contrasts with Boost.Local which had no such problems with installation.) Still, even after doing all of this I ran into a completely incomprehensible error message on my very first attempt, which was to convert the following snippet of code BOOST_LOCAL_FUNCTION( (void) (checkSolution)( (const StandardFormParameters&)(parameters) (const OperatorSpace&)(space) ) ) { checkRegion(Z,space.getOMatrix().slice( parameters.z_bit_diagonal_size ,space.number_of_qubits ,0u ,space.number_of_operators )); } BOOST_LOCAL_FUNCTION_END(checkSolution) forEachStandardFormSolution( number_of_qubits ,number_of_operators ,list_of(EveryColumnHasZ) ,checkSolution ); to forEachStandardFormSolution( number_of_qubits ,number_of_operators ,list_of(EveryColumnHasZ) ,phoenix::bind(checkRegion ,Z ,phoenix::bind(&IntMatrix::slice,phoenix::bind(&OperatorSpace::getOMatrix,arg2) ,phoenix::bind(&StandardFormParameters::z_bit_diagonal_size,arg1) ,phoenix::bind(&OperatorSpace::number_of_qubits,arg1) ,0u ,phoenix::bind(&OperatorSpace::number_of_operators,arg1) ) ) ); (The error message is attached in the file error-1.txt.) I was so overwhelmed that I almost gave up on the whole project, until it suddenly occurred to me that the last line error: no matching function for call to 'get_pointer(const CodeSearch::StandardFormParameters&)' was probably indicating a problem with the member function accessors. I then remembered that I could equivalently replace the above code with forEachStandardFormSolution( number_of_qubits ,number_of_operators ,list_of(EveryColumnHasZ) ,phoenix::bind(checkRegion ,Z ,phoenix::bind(&IntMatrix::slice,phoenix::bind(&OperatorSpace::getOMatrix,arg2) ,phoenix::bind(&StandardFormParameters::z_bit_diagonal_size,arg1) ,number_of_qubits ,0u ,number_of_operators ) ) ); (due to the context in which it appears) and when I did so it compiled just fine. Scratching my head for a moment, I finally realized that the problem was that in my original code I used "arg1" for the last two binds rather than "arg2". For such a simple, easy mistake, the error message is incredibly intimidating. In this example it is hard to say which of the two above versions I prefer. The last version is definitely more compact and feels more "functional" (which is a big plus for me), but it also has a lot of noise. Also, while someone unfamiliar with Boost.Local could look at the first snippet of code and see immediately what it does, I think that even someone who was familiar with Boost.Phoenix would probably need to stare at the second snippet of code for a few moments in order to figure out exactly what it is doing. So while converting to Boost.Phoenix makes the code more compact and more functional, it does also add some obfuscation. I converted a few more snippets of code similar to the above, and then attempted to convert the following from BOOST_LOCAL_FUNCTION( (void) (checkSolution)( (const StandardFormParameters&)(parameters) (const OperatorSpace&)(space) (const bind)((number_of_qubits)(number_of_operators)) ) ) { const unsigned int x_bit_diagonal_size = parameters.x_bit_diagonal_size , z_bit_diagonal_size = parameters.z_bit_diagonal_size ; checkCorrectOrdering( concatenateBoolMatricesVertically( list_of(space.getZMatrix().slice(x_bit_diagonal_size,number_of_qubits,0u,number_of_operators)) (space.getXMatrix().slice(x_bit_diagonal_size,number_of_qubits,0u,x_bit_diagonal_size)) ) ); checkCorrectOrdering( concatenateBoolMatricesVertically( list_of(space.getZMatrix().slice(x_bit_diagonal_size,z_bit_diagonal_size,0u,z_bit_diagonal_size)) (space.getZMatrix().slice(x_bit_diagonal_size,z_bit_diagonal_size,x_bit_diagonal_size,number_of_operators)) ) ); } BOOST_LOCAL_FUNCTION_END(checkSolution) forEachStandardFormSolution( number_of_qubits ,number_of_operators ,column_ordering_only_constraints ,checkSolution ); to forEachStandardFormSolution( number_of_qubits ,number_of_operators ,column_ordering_only_constraints ,phoenix::let (x_bit_diagonal_size = phoenix::bind(&StandardFormParameters::x_bit_diagonal_size,arg1) ,z_bit_diagonal_size = phoenix::bind(&StandardFormParameters::z_bit_diagonal_size,arg1) ,X_matrix = phoenix::bind(&OperatorSpace::getXMatrix,arg2) ,Z_matrix = phoenix::bind(&OperatorSpace::getZMatrix,arg2) ) [phoenix::bind(checkCorrectOrdering ,phoenix::bind(concatenateBoolMatricesVertically ,phoenix::bind( phoenix::bind(list_of ,phoenix::bind(&BoolMatrix::slice,Z_matrix,x_bit_diagonal_size,number_of_qubits,0u,number_of_operators) ) ,phoenix::bind(&BoolMatrix::slice,X_matrix,x_bit_diagonal_size,number_of_qubits,0u,x_bit_diagonal_size) ) ) ) ,phoenix::bind(checkCorrectOrdering ,phoenix::bind(concatenateBoolMatricesVertically ,phoenix::bind( phoenix::bind(list_of ,phoenix::bind(&BoolMatrix::slice,Z_matrix,x_bit_diagonal_size,z_bit_diagonal_size,0u,z_bit_diagonal_size) ) ,phoenix::bind(&BoolMatrix::slice,x_bit_diagonal_size,z_bit_diagonal_size,x_bit_diagonal_size,number_of_operators) ) ) ) ] ); Again I got pages of error messages (included in error-2.txt), and this time I gave up. Even if I could make this compile, the new version is definitely much more obfuscated than the old version. The main reason for this is that the phoenix::bind syntax requires lots of boilerplace when you have chained function calls, i.e. for expressions like A.B().C().D() or f()()()(). In fact, you can see how in the old version I called "space.getZMatrix()" several times (construction is cheap, and this is only a test) rather than creating a local variable Z_matrix to cache the result because I thought it looked nicer, whereas in the second version I cached it in a local variable because to do otherwise would have introduced lots of extra line noise. Also, it is worth mentioning in the documentation that you can't use just any variable names in a "let" block, and that to use the names _a-_z you need to import the namespace boost::phoenix::local_names. I know that this is mentioned in the previous section in the manual, but I had been expecting that all of the information I would need to use "let" would be in the "let" section so I jumped directly there. Having said that, I really do appreciate that one can declare one's own local variable names because otherwise the code above would have been *really* obfuscated. The documentation could be made more clear, though. My first attempt was to put the following lines inside the function just before I needed them: struct x_bit_diagonal_size_key; phoenix::expression::local_variable<x_bit_diagonal_size_key>::type x_bit_diagonal_size; struct z_bit_diagonal_size_key; phoenix::expression::local_variable<z_bit_diagonal_size_key>::type z_bit_diagonal_size; struct X_matrix_key; phoenix::expression::local_variable<X_matrix_key>::type X_matrix; struct Z_matrix_key; phoenix::expression::local_variable<Z_matrix_key>::type Z_matrix; This produced the cryptic error message in error-3.txt. At first I thought that the problem was that I needed curly brackets, but this didn't fix it, so I moved them outside the function, which worked. This was better than nothing, but it was a shame since I had wanted everything that was local to the function (that is, the outer function containing these nested functions) to be inside the (outer) function itself. Also, when I removed the curly brackets (since they were absent in the documentation) I got the even more horrific error message shown in error-4.txt. I then moved on to the following snippet of code: BOOST_LOCAL_FUNCTION( (void) (checkOMatrix)( (const IntMatrix&)(matrix) ) ) { vector<unsigned int> weights; BOOST_FOREACH(const unsigned int row, irange(0u,(unsigned int)matrix.height())) { unsigned int weight = 0; BOOST_FOREACH(const unsigned int col, irange(0u,(unsigned int)matrix.width())) { if(matrix(col,row).val() > 0) ++weight; } weights.push_back(weight); } if(!is_sorted(weights | reversed)) { ostringstream message; message << "Bad weight order:"; for_each(weights,lambda::var(message) << " " << lambda::_1); FATALLY_FAIL(message.str()); } } BOOST_LOCAL_FUNCTION_END(checkOMatrix) forEachOMatrixSolution( number_of_qubits ,number_of_operators ,bind(postWeightRowOrderingConstraintOnRegion,_1,4,_2,BoolVarArgs()) ,checkOMatrix ); I endeavored to translate this into Boost.Phoenix, but it was a bit painful and when I was almost done I realized that there was a macro (FATALLY_FAIL) at the end that probably would not expand into anything that Boost.Phoenix would recognize, so I gave up. At this point I was getting frustrated, and it looked like most of the code would likewise be so painful to translate that I couldn't bring myself to do it for the sake of science. I did, however, stumble on the following code: BOOST_LOCAL_FUNCTION( (void) (checkAllSolutions)( (auto_ptr<OperatorSpace>)(initial_space) (const unsigned int)(start_column) (const unsigned int)(end_column) (const unsigned int)(start_row) (const unsigned int)(end_row) (const bind)((checkSolution)) ) ) { for_each( generateSolutionsFor(initial_space) , bind(checkSolution , _1 , start_column , end_column , start_row , end_row ) ); } BOOST_LOCAL_FUNCTION_END(checkAllSolutions) forEachConstrainedRegion( number_of_qubits , number_of_operators , postConstraint , checkAllSolutions ); If there were ever a case where I wanted something like Phoenix, this was surely it! It was a serious waste to have to write all of that boilerplate code just because I couldn't figure out how to accomplish the same goal using Boost.Bind or Boost.Lambda. So I rewrote it as the following: forEachConstrainedRegion( number_of_qubits , number_of_operators , postConstraint , phoenix::for_each( phoenix::bind(generateSolutionsFor,arg1) ,phoenix::lambda(_a=arg2,_b=arg3,_c=arg4,_d=arg5) [phoenix::bind(checkSolution,arg1,_a,_b,_c,_d)] ) ); Wow, look at how beautiful that is! This is *exactly* the kind of code that I wanted to write, and Phoenix has finally let me write it that way. There's just a little problem... compiling it produces a 7,061 line error message (attached in error-5.txt). Perhaps I just made a simple mistake, but since there was no indication about what I got wrong I simply gave up. Feel free to let me know if you spot my mistake; for your information, my prelude included the lines #include "boost/phoenix/bind.hpp" #include "boost/phoenix/core.hpp" #include "boost/phoenix/scope.hpp" #include "boost/phoenix/stl/algorithm/iteration.hpp" namespace arg_names = boost::phoenix::arg_names; namespace local_names = boost::phoenix::local_names; using local_names::_a; using local_names::_b; using local_names::_c; using local_names::_d; using arg_names::arg1; using arg_names::arg2; using arg_names::arg3; using arg_names::arg4; using arg_names::arg5; so it could be that I made a mistake somewhere in there. In conclusion, I have found that Boost.Phoenix is simply too painful to use in practice for most cases where I have been using Boost.Local. Although it could potentially allow for very elegant code in many cases, it is so hard to figure out what you are doing wrong that it seems to be more trouble than it is worth. I am actually a little sad at having arrived at this conclusion, because the library looked incredibly cool and I was very excited about trying it out, and now I am just walking away from the whole experience feeling incredibly frustrated. Furthermore, even if I were an expert in it I have trouble seeing how in most of the places in my code it would result in code that was either more clear or easier to write. The Boost.Local code has extra noise at the beginning, but when the main body of the nested function contains lots of calls it is far more expressive to write the C++ code directly than to use lots of pheonix::bind functions to accomplish the same thing. This doesn't mean that I think that Boost.Phoenix is a bad library. Reading through the documentation I am absolutely amazed at how it can be used to create very expressive functions; the authors have clearly worked very hard on it and should be proud of their work. However, it simply cannot be treated as invaliding the need for something like Boost.Local, because for one to accomplish many of the same tasks in Boost.Phoenix as one can accomplish in Boost.Local one has to deal with a whole lot of extra mental effort and frustration, and the result at the end is often less expressive and clear (and potentially less maintainable) as it would have been if one had used Boost.Local since the body is no longer expressed in standard C++. I hope that you all find this informative! Cheers, Greg PS: The error-*.txt files are zipped up in error-messages.zip, since unzipped they were ~ 900k which caused this message to be rejected.