Digs into the details of effective generative programming in C++. Major focus on using meta-programming techniques to create efficient, low cyclomatic complexity in artefacts.
2. ACCU 2007
Even in this new millennium, many engineers will still
build components that have very little reuse potential
due to the inflexible way that they were constructed.
This leads to excessive time required to adapt a
component for usage in another system.
3. ACCU 2007
The world of modern
C++ Generative Programming
is a vastly untapped field
(we are just scraping the surface)
4. ACCU 2007
Definition
It is a software engineering paradigm where the aim is
to automatically manufacture highly customised and
optimised intermediate or end-products from
elementary, reusable components by means of
configuration knowledge.
Implies Code Generation
Domain-specific language
Generic Techniques
5. ACCU 2007
Elements of Generative
Programming
Problem space Solution space
Configuration
Knowledge
Illegal feature
combinations
Default settings &
dependencies
Construction rules
Optimisations
Configured ComponentsDomain-specific
concepts
Features
Generic Components
7. ACCU 2007
For effective implementation there is one basic principle
that encompasses most GP strategies:
Dijkstra's Separation of Concerns
This principle accepts the fact that we cannot deal with
many issues at once and that important issues should
be addressed with purpose.
8. ACCU 2007
Key Code-level Strategies
Develop elementary components as generic
components
Fully testable outside of the intended product
configuration
Configure these components using generated artefacts
appropriate to the programming language
Aim for zero cyclomatic-complexity in the generated
artefacts
Eliminate defects as early as possible
9. ACCU 2007
In the large ...
Generative C++ can lead to large number of
configuration classes or meta-classes being
generated.
It is not uncommon to work with 50+ tag classes within
a single type-list
Effective integration of meta-programming, compile-
time paradigms and run-time paradigms are required.
Code-bloat must be avoided!
10. ACCU 2007
Conventions
For the examples assume that the following
namespace aliases exist
using namespace AccuConf;
namespace mpl = AccuConf::mpl;
namespace bmpl = boost::mpl;
All code are implemented in namespace
AccuConf.
16. ACCU 2007
Requirement #5:
Early enforcment
Only allow attribute-operator combinations that have
been defined in DSL
Only allow attributes to be passed that were defined in
DSL
17. ACCU 2007
The conceptual evaluator
Evaluating rules
class Evaluator
{
public:
/**Evaluates the rules against a supplied set
* of attributes
* @param A A collected set of attributes that
will
* be used as input to the rules.
* @return The outcome associated with the rule
* that triggered
*/
Outcome const&
resolve( Properties const& A) const;
};
18. ACCU 2007
The conceptual evaluator
Adding rules
class Evaluator
{
public:
Outcome const&
resolve( Properties const& A) const;
/**Adds a rule to the rule set
* @param O The outcome to return if this rule
* triggers
* @param N The order of this rule in the ruleset
* @param R the rule to add
*/
void add_rule(
Outcome const& O,Order const& N,Rule const& R
);
};
19. ACCU 2007
Idiom #1: Generative Property Bag
What does the Properties class look like?
20. ACCU 2007
Properties class
This can be implemented as a heap-based or a stack-
based approach.
Heap-based:
More memory-efficient, only allocate stored
attributes. Suffers heap-access penalty. Potential
for heap fragmentation.
Stack-based:
Pre-allocate space for all attributes, even if not used.
21. ACCU 2007
Heap-based Properties class
Use boost::any to provide generic storage
Use boost::map to provide conditional storage
Index using meta-indexes
template <typename GeneratedPropSet>
class Properties
{
public:
// To be implemented ...
private:
typedef std::map< int,boost::any > container_type;
container_type m_attrs;
};
24. ACCU 2007
Implementing Property::set
template <typename GeneratedPropSet>
template <typename P>
void Properties<GeneratedPropSet>::set(
typename mpl::property_value<P>::type const& v_
)
{
// Ensure that P is valid !
BOOST_MPL_ASSERT_MSG(
(bmpl::contains<
typename mpl::valid_properties<
GeneratedPropSet >::type,P
>::value),
THIS_IS_NOT_A_VALID_PROPERTY,
(P)
);
// Rest to follow ...
}
Static Assertion provides
relatively useful compile-time
error if a invalid type is supplied
25. ACCU 2007
Implementing Property::set
template <typename GeneratedPropSet>
template <typename P>
void Properties<GeneratedPropSet>::set(
typename mpl::property_value<P>::type const& v_
)
{
// Ensure that P is valid !
BOOST_MPL_ASSERT_MSG(
bmpl::contains<
typename mpl::valid_properties<
GeneratedPropSet >::type,P
>::value,
THIS_IS_NOT_A_VALID_PROPERTY,
(P)
);
// Rest to follow ...
}
boost::mpl::contains is a
metapredicate that returns TRUE
if P is in the list of valid
properties
26. ACCU 2007
Implementing Property::set
template <typename GeneratedPropSet>
template <typename P>
void Properties<GeneratedPropSet>::set(
typename mpl::property_value<P>::type const& v_
)
{
// Ensure that P is valid !
BOOST_MPL_ASSERT_MSG(
(bmpl::contains<
typename mpl::valid_properties<
GeneratedPropSet >::type,P
>::value),
THIS_IS_NOT_A_VALID_PROPERTY,
(P)
);
// Rest to follow ...
}
mpl::valid_properties is our own
metaclass to extract the MPL
sequence of valid property names
from GeneratedPropSet
27. ACCU 2007
Implementing Property::set
template <typename GeneratedPropSet>
template <typename P>
void Properties<GeneratedPropSet>::set(
typename mpl::property_value<P>::type const& v_
)
{
BOOST_MPL_ASSERT_MSG( ... );
// Find key for map
const int pos =
mpl::property_pos< GeneratedPropSet, P >::value
;
// Rest to follow ...
}
mpl::property_pos is our own metaclass
returning the relative position of a type
in a typelist. We use this value as a key
into the map. Due to the MPL_ASSERT
we can be ensured that this is a
valid value.
28. ACCU 2007
Implementing Property::set
template <typename GeneratedPropSet>
template <typename P>
void Properties<GeneratedPropSet>::set(
typename mpl::property_value<P>::type const& v_
)
{
BOOST_MPL_ASSERT_MSG( ... );
// Find key for map
const int pos =
mpl::property_pos< GeneratedPropSet, P >::value
;
// Rest to follow ...
}
Use of 'const int' allows for optimisation
at compile-time
32. ACCU 2007
Implementing Property::get
template <typename GeneratedPropSet>
template <typename P>
typename mpl::property_value<P>::type const&
Properties<GeneratedPropSet>::get() const
{
BOOST_MPL_ASSERT_MSG( ... );
// Find key for map
typename container_type::const_iterator itr =
m_attrs.find(
mpl::property_pos< GeneratedPropSet, P
>::value
);
// Rest to follow ...
}
Reusing mpl::property_pos we obtain
an iterator to the appropiate property.
Note the use of MPL_ASSERT to ensure
a valid index value
33. ACCU 2007
Implementing Property::get
template <typename GeneratedPropSet>
template <typename P>
typename mpl::property_value<P>::type const&
Properties<GeneratedPropSet>::get() const
{
BOOST_MPL_ASSERT_MSG( ... );
... itr = m_attrs.find( ... );
if( m_attrs.end() == itr )
throw range_error("Property not set");
// Rest to follow ...
}
Throw an exception if the property is not
set. Use Property::is_set predicate if a
no_throw check is needed.
(is_set implementation left as an exercise)
34. ACCU 2007
Implementing Property::get
template <typename GeneratedPropSet>
template <typename P>
typename mpl::property_value<P>::type const&
Properties<GeneratedPropSet>::get() const
{
BOOST_MPL_ASSERT_MSG( ... );
... itr = m_attrs.find( ... );
if( m_attrs.end() == itr ) ... ;
typedef typename mpl::property_value<P>::type
value_type;
return *boost::any_cast<value_type>
(&(itr->second));
}
We can assume that
the type stored is
correct. Need to use
the & to invoke a
non-copy version
of any_cast.
35. ACCU 2007
Idiom #2: GGS
Generic-Generated Separation: Access to
generated typelists from generic code should
go through metaclasses.
36. ACCU 2007
MPL metaclasses
Abstract access to type information through
metaclasses
This allows for specialisation when needed
Improves long-term maintenance
41. ACCU 2007
Using Boost for_each
bmpl::for_each<
mpl::valid_properties<Properties>::type
>( FUNCTOR() );
struct FUNCTOR
{
template <typename P>
void operator()(P) const;
};
For each type in
sequence calls the
functor
42. ACCU 2007
Using Boost for_each
bmpl::for_each<
mpl::valid_properties<Properties>::type
>( FUNCTOR() );
struct FUNCTOR
{
template <typename P>
void operator()(P) const;
};
Functor must be able
to accommodate each
type, otherwise
compile-error will occur
48. ACCU 2007
Idiom #5: Hekaton Typelist
Working around limitations in existing typelist
libraries
49. ACCU 2007
Limitations in Boost MPL
sequences
Maximum size of template parameter list is
implementation-dependant
Typically 20
GP might require very long lists
100 types are not uncommon
A special type-list might be needed to convert a
very long generated list into a standard Boost
MPL sequence.
59. ACCU 2007
Compiler Limitations
Few compilers can handle the deep level of
template instantiation very well
gcc 4.x is fast
VC 8 (VS2005) is adequate
gcc 3.x is slow and has big memory footprint
It is not always possible to use the puristic
template solution
Use the Boost Preprocessor Library
Generate code of higher levels of cyclomatic-
complexity
61. ACCU 2007
Touching the void
Pushing C++ skills far beyond the knowledge of
many C++ practitioners
Politics of introducing a new technology
Teaches very good software skills outside the
C++ domain
Am I in the wrong language?
(Yeah, thanks Kevlin!)
Shows requirements for future languages
Powerful syntax
Simplicity of expression
Hinweis der Redaktion
This is a very high level conceptual definition. Most engineers will be more concerned with the techniques for applying configuration knowledge to get to the solution
The zero cyclomatic-complexity aim is the ideal. The real purpose is to eliminate any need to have to debug inside the generated code. This also makes a strong argument for the use of generic programming as an implementation strategy, as all the debugging can be done on the generic components even before anything has been generated.
The zero cyclomatic-complexity aim is the ideal. The real purpose is to eliminate any need to have to debug inside the generated code. This also makes a strong argument for the use of generic programming as an implementation strategy, as all the debugging can be done on the generic components even before anything has been generated.
It is totally possible to have different types depending on the platform. For instance it might be deemed to have WORD instead of uint16_t on a Win32 platform. Types, however, do not have to be OS-specific, types can be varied in order to obtain optimisations in specific products.
It is totally possible to have different types depending on the platform. For instance it might be deemed to have WORD instead of uint16_t on a Win32 platform. Types, however, do not have to be OS-specific, types can be varied in order to obtain optimisations in specific products.