I am reading "C++ Template Metaprogramming" by Abrahams and Gurtovoy.
It is quite difficult to follow, specially because the 'practical'
applications shown are very abstract: parsers, and other stuff.
In this context I have some questions for this group:
o Are you doing template metaprogramming?
o If yes, what applications are you targeting?
o If no why?
Thanks in advance. My objective is to see the end product of
all this stuff, to help me better understand it.
--
jacob navia
jacob at jacob point remcomp point fr
logiciels/informatique
http://www.cs.virginia.edu/~lcc-win32
Welcome to the dark side!
That book certainly isn't what I'd call light reading, but it does
introduce some valuable techniques.
> It is quite difficult to follow, specially because the 'practical'
> applications shown are very abstract: parsers, and other stuff.
I thought you were a compiler writer?
> In this context I have some questions for this group:
>
> o Are you doing template metaprogramming?
Yes.
> o If yes, what applications are you targeting?
So far, fairly simple cases where run time recursion solution would be
use, for example byte order reversal and for typelists (Alexandrescu
"Modern C++ Design").
> Thanks in advance. My objective is to see the end product of
> all this stuff, to help me better understand it.
Template Metaprogramming is still a technique in its infancy and not yet
widely used, probably because (as you have discovered) it takes a log of
getting used to.
--
Ian Collins
> I am reading "C++ Template Metaprogramming" by Abrahams and Gurtovoy.
Good stuff.
> It is quite difficult to follow, specially because the 'practical'
> applications shown are very abstract: parsers, and other stuff.
The "parsers" are only mentioned to give you a feel for the difference
between DSLs and DSELs, both of which happen to be exemplified by
syntactically similar libraries (yacc and Spirit). If you understand
the binary example in the front, you get the idea. The most important
part of the rest of the book (IMO) is how to use existing TMP libraries,
so that you don't have to write so much boilerplate syntax to use TMP.
> In this context I have some questions for this group:
>
> o Are you doing template metaprogramming?
Yes. Most of the code I write isn't exactly metaprogramming, but when
it fits the problem, it's fantastic.
> o If yes, what applications are you targeting?
I develop CAD software for the semiconductor industry. So far, I've
used TMP mainly to automatically generate data structures that map well
to particular semantic requirements. I also parameterize a lot of code
with policy types, and recently used TMP to enable some simple
aspect-oriented programming (which raw C++ doesn't support very well).
> Thanks in advance. My objective is to see the end product of
> all this stuff, to help me better understand it.
The tricky part is figuring out how much metaprogramming is actually
worthwhile on a particular project, because when you're starting out,
you don't have any feel for how long a given metaprogram will take you
to write. It's always unpleasant to spend days on a piece of code, only
to realize that it's not quite what you need after all. Things get a
lot clearer with experience. As evidenced by most of C++ (or Linux, or
Vim, or whiskey, music, or art), some of the most worthwhile endeavors
have the steepest learning curves.
I did some from 1997-99
> o If yes, what applications are you targeting?
scientific computation (project Scientific Library in C++ aka SL++)
The project is unmaintained since then but is still the first hit with
google for SL++ ;-)
> o If no why?
because of lack of time and change of technology ;-)
> Thanks in advance. My objective is to see the end product of
> all this stuff, to help me better understand it.
TMP is useful for adding syntactic sugar to you code/libraries (DSL)
and/or optimize your code by generating more efficient constructions
(e.g. avoiding to build temporaries by flattening/fusing expressions).
Both are generally used in parallel. If you are not interested by
either of these aspects, it's of little use. But do not underestimate
the usefulness of syntactic sugar (e.g. through operator overloading).
The drawbacks are longer compilation time and code slightly more
difficult to understand (and debug) if you are not trained to template
specialization and template expression. Looking at a library which
provides support for generic TMP (like Boost MPL) is a good starting
point.
a+, ld.
Yes
> o If yes, what applications are you targeting?
Extending the type system to encompass compile time dimensional analysis.
A generic but compile time constructed record structure for building
field editors, field IO, etc, that's type safe and extensible.
I more regularly use some of the techniques learned in that book in
generic programming terms.
I'd be interested in how.
Here's a complete, but over-simplified, sample program. I apologize for
the length of the post; the syntax is verbose, and I included a lot of
comments.
/* In this example, we're just instrumenting nullary functions with a
* couple of similar-but-different aspects. Behavior is associated with
* aspects through "policies" of the Modern C++ Design genre.
*
* I've recently started using the MPL naming conventions for wrapped
* values and types, but I'm (mostly) not doing so here. I am using
* some STL conventions, though: each functor is copyable, and exposes a
* single return type as result_type.
*/
#include <iostream> /* To make this example shiny. */
/* Provide default values for the extension points. As usual in C++,
* it's up to the library developer to decide what can be customized,
* and what's written in stone.
*
* Here, I've only defined two extension points. These would ordinarily
* be "do-nothing" policies, which I leave as an exercise to the reader.
* The requirements of policies defined here are pretty simple, so the
* user could write their own if the muse struck.
*
* The important thing about defaults is that they let new aspects be
* added to the library, without requiring any changes to client code.
*/
#define CLCPP_ASPECT_EP_ON_RETURN \
aspect::on_return::log_result
#define CLCPP_ASPECT_EP_ON_EXCEPTION \
aspect::on_exception::return_trivial
/* Automate the production of site-specific traits class
* specializations.
*
* This macro is absurdly big and rigid; e.g., it can only be used at
* global scope, and is ripe for functional decomposition. I wrote it
* this way for didactic purposes. My production code is a lot more
* modular.
*/
#define CLCPP_ASPECT_NULLARY_FUNCTION( return_type, name ) \
\
namespace aspect { \
namespace impl { \
\
/* Forward-declare the implementation function whose body the
* client will actually provide, so our wrapper function can use
* it. This may prevent inlining, but it's a stupid-simple way
* to support user-friendly syntax.
*/ \
return_type name##_##impl(); \
\
/* I generally write the back end in terms of types, then
* provide wrappers for raw functions and values. This lets me
* support (e.g.) functors and raw functions with the same code.
*/ \
template<> \
struct name_of< \
wrapped_nullary_function< \
return_type, name##_##impl>::tag> \
{ \
operator char const* () { \
return #name; \
} \
}; \
} \
} \
\
/* Define an "interface" function with the name and signature
* expected by the client. I'm not bothering with throw-specs, and
* frankly, I don't think much about them in production code,
* either. The interface function just decorates an
* "implementation" function with selected policies. Obviously, not
* all aspects can be implemented the same way, but all policies for
* a given aspect have to support common syntax. There is usually
* an obvious place to put the hooks for a given aspect.
*/ \
return_type name() { \
return \
CLCPP_ASPECT_EP_ON_RETURN< \
CLCPP_ASPECT_EP_ON_EXCEPTION< \
aspect::impl::wrapped_nullary_function < \
return_type, \
aspect::impl::name##_##impl> > >( )(); \
} \
\
/* The client will define the body of the impl function. */ \
return_type aspect::impl::name##_##impl()
namespace aspect {
/* Declare templates whose specializations represent local values of
* a particular trait. I use these quite a bit, not just for AOP.
* Instantiations of these templates represent the boundary between
* the preprocessor and template metaprograms.
*
* One thing to note is that the traits templates are specialized
* for tag types, rather than direct inputs from the client. If I
* specialize a trait for a class T, I also want that specialization
* to be the default for any subclasses of T. The tag type is
* perfect for achieving this, since it is inherited by default, but
* can optionally be overidden by subclasses.
*/
namespace impl {
template<class T>
struct name_of: name_of<typename T::tag>
{ };
template<class Result_type, Result_type (Function)()>
struct wrapped_nullary_function
{
typedef Result_type result_type;
struct tag
{ };
result_type operator()() const {
return Function();
}
};
}
/* Define some policies to define possible behaviors for a
* particular aspect. This aspect is called "on_exception," and
* corresponds to local exception handling. You could have a
* completely different aspect represent top-level error-handling,
* if you so chose.
*/
namespace on_exception {
template<class Functor>
struct log_and_rethrow: Functor
{
typedef typename Functor::result_type result_type;
result_type operator()() const try {
return Functor::operator()();
} catch (...) {
std::cerr
<< impl::name_of<Functor>( )
<< ": throwing exception\n";
throw;
}
};
/* Convert exceptions to trivial value returns. I recently did
* something similar in reverse, recognizing POSIX error codes
* and converting them automatically to exceptions.
*/
template<class Functor>
struct return_trivial: Functor
{
typedef typename Functor::result_type result_type;
result_type operator()() const try {
return Functor::operator()();
} catch (...) {
return result_type( );
}
};
}
/* Define a slightly different aspect. In the real world, the
* policies here would have to given special treatment to functions
* returning void.
*/
namespace on_return {
template<class Functor>
struct log_result: Functor
{
typedef typename Functor::result_type result_type;
static result_type log_and_return(
result_type const& result) {
std::clog
<< impl::name_of<Functor>( )
<< ": returning " << result << '\n';
return result;
}
result_type operator()() const {
return log_and_return(Functor( )());
}
};
/* Here's a little code to generate new policies with a specific
* pattern: Given a multiplier, generate an on_return policy to
* multiply functions' return values by that multiplier. A
* little more metaprogramming could make this more versatile,
* e.g. it could accept an operation as a parameter instead of
* hard-coding multiplication, or it could make multiplication
* mean different things for different result_types (e.g. to
* replicate strings).
*
* Th nested template is a form of lexical closure. There's no
* native support for it in run-time C++, but it works well in
* TMP. I first saw the TMP version in the STL allocator's
* "rebind" metafunction, and didn't really get it, even though
* I already understood and used closures in Perl. It helps to
* think of compile-time C++ as a domain-specific scripting
* language.
*
* The _c is an MPL convention. In production code, I would
* leverage mpl::times_c, to benefit from any specializations.
*/
template<int Factor>
struct multiply_c
{
template<class Functor>
struct apply
{
typedef typename Functor::result_type result_type;
result_type operator()() const {
return Factor * Functor( )();
}
};
};
}
}
/* The client can set policies for the aspects using the given extension
* points. Here, we're overriding one policy, and letting the other
* retain its default.
*
* MPL supports lambda expressions that would let the client omit the
* ::apply.
*/
#undef CLCPP_ASPECT_EP_ON_RETURN
#define CLCPP_ASPECT_EP_ON_RETURN \
aspect::on_return::multiply_c<2>::apply
/* #undef CLCPP_ASPECT_EP_ON_EXCEPTION
* #define CLCPP_ASPECT_EP_ON_EXCEPTION \
* aspect::on_exception::log_and_rethrow
*/
/* The client code here looks a little hideous, because (1) the macro
* invocations dominate these tiny functions, and (2) the function
* signatures aren't in the usual C++ format. It is not hard to avoid
* these issues by letting the client define each implementation
* function manually, then register it with the system in a separate
* statement. That would have the benefit of delaying the interface
* function's definition until after the implementation function's
* definition, and would likely encourage inlining. I've just forced
* everything into one-liners here for brevity.
*
* Remember that all of this stuff happens at compile-time, including
* any function registration. By the time the run-time program starts,
* all of the logic used to configure the aspects and insert the
* relevant call-backs (policies) throughout the program is long since
* complete. The policy member functions are implicitly inline, and if
* empty, they are elided altogether. Run-time performance is snappy,
* errors are caught early, and all is right with the world.
*/
CLCPP_ASPECT_NULLARY_FUNCTION(int, do_something) {
return 42;
}
CLCPP_ASPECT_NULLARY_FUNCTION(double, do_something_else) {
std::cout << "hello\n";
throw "world";
return 37.2;
}
int main() {
std::cout << do_something() << '\n';
std::cout << do_something_else() << '\n';
}
Thanks! I'll give this some study.
> A generic but compile time constructed record structure for building
> field editors, field IO, etc, that's type safe and extensible.
>
Could you show a synopsis on how you're doing this?
Isn't that sort of the poster child for TMP?
http://www.informit.com/articles/article.aspx?p=375705
http://se.ethz.ch/~meyer/publications/OTHERS/scott_meyers/dimensions.pdf
Not that much anymore. Has been pretty much solved in the boost library.
>
>> A generic but compile time constructed record structure for building
>> field editors, field IO, etc, that's type safe and extensible.
>>
>
> Could you show a synopsis on how you're doing this?
The following is some scratch I put together for a presentation at a
local college. It's functional and almost complete. It doesn't address
problems you'll have with namespaces and using the MSVC method in g++ or
others.
#include <iostream>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/begin.hpp>
#include <boost/mpl/next.hpp>
#include <boost/mpl/for_each.hpp>
//#define MSVC // uncomment if you have visual studio. Also fine in g++
#define DECLARE_FIELD(NAME,TYPE) \
struct NAME \
{ \
typedef TYPE type; \
static char const* name() { return #NAME ; } \
}
template < typename CONT, typename FIELD, typename TYPE, TYPE CONT::*addr >
struct record_field_desc
{
typedef CONT member_of;
typedef FIELD field;
typedef TYPE type;
static TYPE get(CONT const& cont) { return cont.*addr; }
static void set(CONT & cont, TYPE const& value) { cont.*addr = value; }
};
template < typename REC >
struct meta
{
typedef typename REC::meta type;
};
#ifndef MSVC
#define BEGIN_META(CONT) \
struct meta { typedef boost::mpl::vector<CONT
#define ADD_FIELD(CONT, FIELD, ADDR) \
, record_field_desc<CONT, FIELD, FIELD::type, ADDR>
#define END_META() \
> fields; \
typedef boost::mpl::next<boost::mpl::begin<fields>::type>::type begin; \
typedef boost::mpl::end<fields>::type end; \
};
#else
#define BEGIN_META(CONT) \
struct CONT_meta \
{ \
typedef boost::mpl::vector<CONT
#define ADD_FIELD(CONT, FIELD, ADDR) \
, record_field_desc<CONT, FIELD, FIELD::type, ADDR>
#define END_META(CONT) \
> fields; \
typedef boost::mpl::next< boost::mpl::begin<fields>::type >::type begin; \
typedef boost::mpl::end<fields>::type end; \
}; \
\
template <> struct meta<CONT> { typedef CONT_meta type; };
#endif
DECLARE_FIELD(test1, int);
DECLARE_FIELD(test2, double);
DECLARE_FIELD(test3, long);
struct test_record
{
int x;
double y;
#ifndef MSVC
BEGIN_META(test_record)
ADD_FIELD(test_record, test1, &test_record::x)
ADD_FIELD(test_record, test2, &test_record::y)
//ADD_FIELD(test_record, test3, &test_record::y) // uncomment to
see error
END_META()
#endif
};
#ifdef MSVC
BEGIN_META(test_record)
ADD_FIELD(test_record, test1, &test_record::x)
ADD_FIELD(test_record, test2, &test_record::y)
END_META(test_record)
#endif
// UTILITY STUFF
template < typename T >
struct type_desc
{
static char const* desc() { return typeid(T).name(); }
};
template <>
struct type_desc<int> { static char const* desc() { return "integer"; } };
template <>
struct type_desc<double> { static char const* desc() { return "real
number"; } };
template < typename REC >
struct record_reader
{
struct field_reader
{
REC & rec;
field_reader(REC & r) : rec(r) {}
template < typename FIELD_DESC >
void operator() (FIELD_DESC const&) const
{
typedef typename FIELD_DESC::type type;
std::cout << "Enter a " << type_desc<type>::desc() << " for " <<
FIELD_DESC::field::name() << ": ";
type t;
std::cin >> t;
FIELD_DESC::set(rec, t);
}
};
static REC read_record()
{
REC rec;
boost::mpl::for_each< typename meta<REC>::type >(field_reader(rec));
return rec;
}
};
template < typename REC >
struct record_writer
{
struct field_writer
{
REC const& rec;
field_writer(REC const& r) : rec(r) {}
template < typename FIELD_DESC >
void operator() (FIELD_DESC const&) const
{
std::cout << "Value for "
<< FIELD_DESC::field::name()
<< " is: " << FIELD_DESC::get(rec) << std::endl;
}
};
static void write_record(REC const& r)
{
boost::mpl::for_each< typename meta<REC>::type >(field_writer(r));
}
};
int main()
{
test_record rec = {};
record_writer<test_record>::write_record(rec);
rec = record_reader<test_record>::read_record();
record_writer<test_record>::write_record(rec);
}