views:

318

answers:

3

I am documenting some code which uses meta-programming heavily, for example:

 template<rysq::type A, rysq::type B, rysq::type C, rysq::type D, class Transform>
 struct Kernel<meta::braket<A,B,C,D>, Transform,
               typename boost::enable_if<
                   quadrature<meta::braket<A,B,C,D>, Transform> >::type>
 : Eri <Transform> {

what is a good way to document such construct using doxygen?

A: 

The meta-programming seems to implement maths. I would write the describing mathematical formulas with latex escapes in the doxygen documentation.

Peter G.
hi. I may not made myself clear, I need to document programming logic, rather than mathematical description.
aaa
@aaa I'd say you need to document both.
anon
@aaa What programming logic remains the in shown fragment when you strip it of C++ and meta-programming technicialities?
Peter G.
@Peter what do you mean? I am not following quite
aaa
@aaa I am not well enough versed in template meta-programming to see programming logic in the shown fragment. Never mind. I should better leave this to the experts.
Peter G.
+3  A: 

Use preprocessor macros. Here's an example from the not-yet-official Boost.XInt library (presently queued for review for inclusion in Boost):

#ifdef BOOST_XINT_DOXYGEN_IGNORE
    // The documentation should see a simplified version of the template
    // parameters.
    #define BOOST_XINT_INITIAL_APARAMS ...
    #define BOOST_XINT_CLASS_APARAMS ...
    #define BOOST_XINT_CLASS_BPARAMS other
    #define BOOST_XINT_APARAMS ...
    #define BOOST_XINT_BPARAMS other
#else
    #define BOOST_XINT_INITIAL_APARAMS \
        class A0 = parameter::void_, \
        class A1 = parameter::void_, \
        class A2 = parameter::void_, \
        class A3 = parameter::void_, \
        class A4 = parameter::void_, \
        class A5 = parameter::void_
    #define BOOST_XINT_CLASS_APARAMS class A0, class A1, class A2, class A3, \
        class A4, class A5
    #define BOOST_XINT_APARAMS A0, A1, A2, A3, A4, A5
    #define BOOST_XINT_CLASS_BPARAMS class B0, class B1, class B2, class B3, \
        class B4, class B5
    #define BOOST_XINT_BPARAMS B0, B1, B2, B3, B4, B5
#endif

Use the #defined macro names instead of the template parameters, everywhere you need them, like so:

/*! \brief The integer_t class template.

This class implements the standard aribitrary-length %integer type.

[...lots more documentation omitted...]
*/
template<BOOST_XINT_INITIAL_APARAMS>
class integer_t: virtual public detail::integer_t_data<BOOST_XINT_APARAMS>,
    public detail::nan_functions<detail::integer_t_data<BOOST_XINT_APARAMS>::
    NothrowType::value, // ...lots more base classes omitted...
{
    // ...etcetera

And put lines like these in the Doxyfile:

PREDEFINED             = BOOST_XINT_DOXYGEN_IGNORE

EXPAND_AS_DEFINED      = BOOST_XINT_INITIAL_APARAMS \
                         BOOST_XINT_CLASS_APARAMS \
                         BOOST_XINT_CLASS_BPARAMS \
                         BOOST_XINT_APARAMS \
                         BOOST_XINT_BPARAMS

The result is that Doxygen sees either "..." or "other" for the template parameters, and the compiler sees the real ones. If you describe the template parameters in the documentation for the class itself, then the user of the library will only need to see them in the one place that he's likely to look for them; they'll be hidden everywhere else.

As an additional advantage to this design, if you ever need to make changes to the template parameter lists, you only need to change them in the macro definitions and the functions that actually use the changed parameters. Everything else will adapt automatically.

Head Geek
+1  A: 

Here is my take on it:

///
/// \defgroup Kernel Kernel
///
/// \brief Kernel does this and that
/// \{

/// \brief Kernel template class brief description.
template<Braket,Transform,Boolean>
struct Kernel
{};

/// \brief Kernel partial template specialization brief description.
///
/// More detailed description...<br>
/// Partially specializes Kernel with meta::braket<A,B,C,D\>.<br>
/// If quadrature<meta::braket<A,B,C,D\>, Transform\> is true then enable
/// this algorithm, otherwise enable this other algorithm.<br>
/// Inherits privately from template class Eri<Transform\><br>
/// \tparam A           template parameter A of type rysq::type, documentation and concepts
/// \tparam B           template parameter B of type rysq::type, documentation and concepts
/// \tparam C           template parameter C of type rysq::type, documentation and concepts
/// \tparam D           template parameter D of type rysq::type, documentation and concepts
/// \tparam Transform   template parameter class Transform documentation and concepts
/// \see Kernel\<Braket,Transform,Boolean\>
/// \see Eri
/// \see meta::braket
/// \see quadrature
#ifdef DOXY
// This is the documentation version
template<A,B,C,D,Transform>
struct Kernel<Braket,Transform,Boolean>
#else
// This is what gets compiled
template<rysq::type A, rysq::type B, rysq::type C, rysq::type D, class Transform>
struct Kernel<meta::braket<A,B,C,D>, Transform,typename boost::enable_if<quadrature<meta::braket<A,B,C,D>, Transform> >::type>
#endif
: Eri <Transform> {};

/// \}

Don't forget to add DOXY in the PREDEFINED section of Doxygen's preprocessor.

I usually prefer to hide implementation details from the user of my code, so I change what Doxygen sees. In this case, you will find all your specializations under one group, the Kernel group, and under the class list, all specializations will be grouped together and won't have a very long and un-comprehensible name.

Hope it helps.

LeSnip3R