Typically, in some form of C++ best practice summaries, it is recommended to stay away from using the C++ Macro Preprocessor. For the most part, except when I needed to Microsoft MFC message maps, where use preprocessor macros, I have followed this maxim. Until now.
Sunday, April 26. 2009
Boost Preprocessor: Arrays
I came across a situation where one section of code is dependent upon the order of declarations in another section of code. With manual code preparation, and even if things are documented appropriately, it is easy to forget to update the inter-related sections of code properly.
An example is when initializing the column definitions of an MFC CListView. I'd like to construct an enumeration of column indexes and ensure those remain in-sync with any changes I may make to the CListView column defintions themselves.
I hadn't realized the power of the C++ macro preprocessor until I started reading Appendix A: An Introduction to Preprocessor Metaprogramming in the book "C++ Template Metaprogramming" by David Abrahams and Aleksey Gurtovoy.
By using the Boost Preprocessor Library, the power of the C++ Macro Preprocessor is realized.
I can now define my column structures and associated variables in a single header file. I also define various extraction macros.
#include "boost/preprocessor/tuple/elem.hpp" #include "boost/preprocessor/array/elem.hpp" #include "boost/preprocessor/array/size.hpp" #include "boost/preprocessor/punctuation/comma_if.hpp" #include "boost/preprocessor/repetition/repeat.hpp" #define COLHDR_DELTAS_ARRAY_ELEMENT_SIZE 6 #define COLHDR_DELTAS_ARRAY \ (15, \ ( \ (COLHDR_DELTAS_COL_UndSym, "UndSym", LVCFMT_LEFT, 50, std::string, m_sSymbolUnderlying), \ (COLHDR_DELTAS_COL_Sym , "Sym", LVCFMT_RIGHT, 50, std::string, m_sSymbol), \ (COLHDR_DELTAS_COL_Strk , "Strk", LVCFMT_RIGHT, 50, double, m_dblStrike), \ (COLHDR_DELTAS_COL_Expiry, "Expiry", LVCFMT_RIGHT, 50, ptime, m_dtExpiry), \ (COLHDR_DELTAS_COL_Bid , "Bid", LVCFMT_RIGHT, 50, double, m_dblBid), \ (COLHDR_DELTAS_COL_BidSz , "BidSz", LVCFMT_RIGHT, 50, int, m_nBidSize), \ (COLHDR_DELTAS_COL_Sprd , "Sprd", LVCFMT_RIGHT, 50, double, m_dblSpread), \ (COLHDR_DELTAS_COL_Ask , "Ask", LVCFMT_RIGHT, 50, double, m_dblAsk), \ (COLHDR_DELTAS_COL_AskSz , "AskSz", LVCFMT_RIGHT, 50, int, m_nAskSize), \ (COLHDR_DELTAS_COL_Pos , "Pos", LVCFMT_RIGHT, 50, int, m_nPosition), \ (COLHDR_DELTAS_COL_AvgCst, "AvgCst", LVCFMT_RIGHT, 50, double, m_dblAverageCost), \ (COLHDR_DELTAS_COL_Delta , "Delta", LVCFMT_RIGHT, 50, double, m_dblDelta), \ (COLHDR_DELTAS_COL_Gamma , "Gamma", LVCFMT_RIGHT, 50, double, m_dblGamma), \ (COLHDR_DELTAS_COL_UnRlPL, "UnRlPL", LVCFMT_RIGHT, 50, double, m_dblUnrealizedPL), \ (COLHDR_DELTAS_COL_RlPL , "RlPL", LVCFMT_RIGHT, 50, double, m_dblRealizedPL) \ ) \ ) \ /**/ #define COLHDR_DELTAS_EXTRACT_COL_DETAILS(z, n, m, text) \ BOOST_PP_TUPLE_ELEM( \ COLHDR_DELTAS_ARRAY_ELEMENT_SIZE, m, \ BOOST_PP_ARRAY_ELEM( n, COLHDR_DELTAS_ARRAY ) \ ) #define COLHDR_DELTAS_EXTRACT_ENUM_LIST(z, n, text) \ BOOST_PP_COMMA_IF(n) \ COLHDR_DELTAS_EXTRACT_COL_DETAILS( z, n, 0, text ) #define COLHDR_DELTAS_EMIT_InsertColumn( z, n, VAR ) \ m_vuDeltas.InsertColumn( VAR++, \ _T(COLHDR_DELTAS_EXTRACT_COL_DETAILS(z, n, 1, ~)), \ COLHDR_DELTAS_EXTRACT_COL_DETAILS(z, n, 2, ~), \ COLHDR_DELTAS_EXTRACT_COL_DETAILS(z, n, 3, ~) \ ); #define COLHDR_DELTAS_EMIT_DefineVars( z, n, text ) \ COLHDR_DELTAS_EXTRACT_COL_DETAILS(z, n, 4, ~) \ COLHDR_DELTAS_EXTRACT_COL_DETAILS(z, n, 5, ~)\ ;
Then in my class declaration, I can extract the enumerations in the correct 0-based order:
enum enumColHdrDeltasCol { BOOST_PP_REPEAT( BOOST_PP_ARRAY_SIZE( COLHDR_DELTAS_ARRAY ), COLHDR_DELTAS_EXTRACT_ENUM_LIST, ~ ) };
The repetitive code of creating the columns in the CListView is handled through repetition and extraction macros:
int ix = 0; BOOST_PP_REPEAT( BOOST_PP_ARRAY_SIZE( COLHDR_DELTAS_ARRAY ), COLHDR_DELTAS_EMIT_InsertColumn, ix ) // m_vuDeltas.InsertColumn( ix++, "UndSym", LVCFMT_LEFT, 50 );
I'll be able to further use the initial structure to create the row factory for keeping the CListView and row-structures synchronized. If I happen to change my mind on column ordering, all related code sections are automatically updated.