Boost logo

Boost :

From: Tobias Schwinger (tschwinger_at_[hidden])
Date: 2007-08-10 16:05:38


Ion Gaztañaga wrote:
> Tobias Schwinger wrote:
>> Ion Gaztañaga wrote:
>> Also, please keep in mind that we are talking about a workaround,
>> because decent compilers should factor out redundant machine code
>> automatically.
>
> Do you have any evidence of this? I've read somewhere that Visual 8 does
> something like this, but I hope this is not the COMDAT folding option.
> This redundant machine code erasing is performed by the linker, the code
> is still instantiated at compile time, so we are not going to save much
> compilation time.

No, it's not some new esoteric thing: Given some code like

     template<typename T> struct X
     { /* code inside that only uses T* */ }

the compiler can determine that an incomplete type suffices for 'T' and
that instances for any 'T' are interchangeable.

Let' see:

     Last login: Fri Aug 10 16:48:12 on ttyp2
     Welcome to Darwin!
     max:~ tosh$ cd ~/Lab/consulting/
     max:~/Lab/consulting tosh$ cat code_bloat.cpp

     template< typename T >
     struct X
     {
       T* a;
       T* b;

       void swap()
       {
         T* tmp = a;
         a = b;
         b = tmp;
       }
     };

     struct t1;
     #ifdef TRY_BLOAT
     struct t2;
     struct t3;
     struct t4;
     struct t5;
     struct t6;
     struct t7;
     struct t8;
     struct t9;
     #endif

     int main()
     {
       { X<t1> x; x.swap(); }

     #ifdef TRY_BLOAT
       { X<t2> x; x.swap(); }
       { X<t3> x; x.swap(); }
       { X<t4> x; x.swap(); }
       { X<t5> x; x.swap(); }
       { X<t6> x; x.swap(); }
       { X<t7> x; x.swap(); }
       { X<t8> x; x.swap(); }
     #else
       { X<t1> x; x.swap(); }
       { X<t1> x; x.swap(); }
       { X<t1> x; x.swap(); }
       { X<t1> x; x.swap(); }
       { X<t1> x; x.swap(); }
       { X<t1> x; x.swap(); }
       { X<t1> x; x.swap(); }
     #endif

       return 0;
     }

     max:~/Lab/consulting tosh$ g++ -O2 code_bloat.cpp ; ls -l a.out
     -rwxr-xr-x 1 tosh tosh 13928 Aug 10 19:26 a.out
     max:~/Lab/consulting tosh$ g++ -O2 code_bloat.cpp -DTRY_BLOAT ; ls
-l a.out
     -rwxr-xr-x 1 tosh tosh 13928 Aug 10 19:26 a.out
     max:~/Lab/consulting tosh$ # here we go

> In our example (list), all produced functions should be exactly the
> same, so the linker might have it easier (I don't know if shared
> libraries can also be optimized)

> to erase redundant functions. However,

In terms of file size: Of course you can't throw out code that might not
be present elsewhere ;-).

In terms of memory footprint: Well, at least there are realtime OSes
that support function level loading...

> the two level architecture (node<->value ) would still be necessary,
> because functions are not exactly equal (the final transformation
> between the node and the hook is not equal, unlike the core algorithm).

Sure, we want to minimize the type dependencies of the node hook.

>> Second, the current usage is quite far away from "normally specifying
>> template arguments", isn't it?
>
> Well, for an average C++ programmer, a template parameters list
>
> template <class ValueTraits, bool ConstantTimeSize, ...>
>
> is surely easier to understand that seeing something like
>
> template<class Policy1 = no_policy, class Policy2 = no_policy, >
>
> and having to write
>
> class_name
> <class T
> ,policy_name_a<parameter_to_policy2>,
> ,policy_name_a<parameter_to_policy3>...
> >
>
> specially when we only have 3 template parameters.

I want to understand the code without having to dig up the library header.

The first argument to a container is usually the type, the other ones
are >>gotta read into that library<<. For a set you can probably figure
the second to be a compare function. For configuration options, however,
positional arguments are a bad idea, IMO, because the resulting client
code will not be self-explanatory. See

     http://www.boost-consulting.com/about/vision/expressiveness

. Code is read more often than it is written. According to quite recent
studies, 80% of software engineering efforts are -surprise surprise-
maintainance.

> I don't consider myself a novice, but I still don't easily grok this
> style (surely because no standard C++ utility uses this approach and I'm
> not used to it).
>
> Anyway the derivation approach of your attached code should minimize
> instantiation problems. All the instantiated code is the same for all
> policy combinations, and only the front-end class is different.
>
> Another question is if having different C++ types for otherwise
> semantically exact concepts (just that policies have been specified in
> another order) is a problem. Even if it's not a problem, that makes me
> really nervous.

Holding the implementation and using "dumb inline forwarders" (as
suggested as the last resort method in the code sample) is documented to
work in "C++ Templates" [Vandevoorde, Josuttis] as a technique to reduce
code bloat.

> That's why I think something like:
>
> typedef options
> < void_pointer<void*>
> , constant_time_size<false>
> >::type my_options;
>
> list<T, my_options>
>
> is more straightforward and understandable, since the declaration of the
> class is something like:
>
> template<class T, class Options = default_list_options>
> class list;
>
> Any C++ programmer would instantly understand how to use the class ("Oh,
> a container of type T, where I can tweak some options"). I know, I'm
> from the old school, but you know: "One concept, one type" ;-)

<...>

> Variant 2 can be very similar to variant 3 (we don't need an external
> options type and derive from default options). Just do the argument
> processing outside the class so that we don't complicate the class with
> argument processing:
>
> typedef
> container
> < T
> , list_options
> < constant_time_size_operation<false>
> , void_pointer_type<offset_ptr<void>
> ,... // Unspecified options take default values
> >
> >::type >
> MyContainer;
>
> where container is:
>
> template<class T, class ListOptions = list_options<> >
> class container;
>
> Container is exactly the same type for the same options and my
> nervousness disappears ;-)

That would be a great improvement of the interface.

The reason why I suggested using a different approach is that
'list_options' is a non-trivial metaprogram which you said you wanted to
avoid (see attached code).

I'm still not convinced it is the better solution, but hey - it's your
party ;-).

Regards,
Tobias


#include <boost/mpl/vector.hpp>
#include <boost/mpl/int.hpp>
#include <boost/mpl/sort.hpp>
#include <boost/mpl/fold.hpp>
#include <boost/mpl/placeholders.hpp>

#ifndef NDEBUG
#include <boost/mpl/unique.hpp>
#include <boost/mpl/assert.hpp>
#include <boost/mpl/equal_to.hpp>
#include <boost/mpl/size.hpp>
#endif

namespace mpl = boost::mpl;

// Defaults

namespace detail
{
    struct container_xyz_defaults
    {
        static bool const constant_time_size = true;

        typedef void tag;

        template<typename T>
        struct value_traits
        {
            typedef T* pointer_type;
            typedef T& reference;
            // ...
        };
        // ...
    };
} // namespace detail

// Setters

template<bool Enabled>
struct constant_time_size
{
    template<class Base>
    struct apply_options : Base
    {
        static bool const constant_time_size = Enabled;
    };

    static int const option_id = 1;
};

template<typename T>
struct tag
{
    template<class Base>
    struct apply_options : Base
    {
        typedef T tag;
    };

    static int const option_id = 2;
};

template<typename T>
struct offset_ptr {}; // just pretend it's there...

struct offset_ptr_storage
{
    template<class Base>
    struct apply_options : Base
    {
        template<typename T>
        struct value_traits
        {
            typedef offset_ptr<T> pointer_type;
            typedef T& reference;
            // ...
        };
    };

    static int const option_id = 3;
};

// ...

namespace detail
{
    struct option_id_less
    {
        template<class U, class V>
        struct apply
        {
            typedef bool value_type;
            typedef apply<U,V> type;
            static bool const value = U::option_id < V::option_id;
        };
    };

    struct option_id_equal
    {
        template<class U, class V>
        struct apply
        {
            typedef bool value_type;
            typedef apply<U,V> type;
            static bool const value = U::option_id == V::option_id;
        };
    };

    struct non_empty_option
    {
        template<class T>
        struct apply
        {
            typedef bool value_type;
            typedef apply<T> type;
            static bool const value = !! T::option_id;
        };
    };

    struct combine_options
    {
        template<class U, class V>
        struct apply
        {
            typedef typename V::template apply_options<U> type;
        };
    };
}

typedef mpl::na none_specified;

template<class Option1 = none_specified, class Option2 = none_specified,
    class Option3 = none_specified, class Option4 = none_specified>
class container_xyz_options
{
    // set up sequence, sort
    typedef mpl::vector<Option1,Option2,Option3,Option4> options;
    typedef typename mpl::sort<options,detail::option_id_less>::type bases;

#ifndef NDEBUG
    // check the options make sense
    typedef typename mpl::unique<bases,
        detail::option_id_equal>::type unique;
    typedef mpl::equal_to< mpl::size<bases>, mpl::size<unique> > unambiguous;
    BOOST_MPL_ASSERT_MSG(unambiguous::value, CONFLICTING_OPTIONS_SPECIFIED,(bases));
#endif
    
    // join options
  public:
    typedef typename mpl::fold<bases,detail::container_xyz_defaults,
        detail::combine_options>::type type;
};

template<typename T, class Options =
    typename container_xyz_options<>::type >
class container_xyz
{
  public:

    typedef typename Options::template value_traits<T> value_traits;
    typedef typename value_traits::pointer_type pointer_type;
    typedef typename Options::tag tag;
    static bool const constant_time_size = Options::constant_time_size;
    // ...

};

// Demonstrate it

#include <iostream>
#include <typeinfo>

template<class C>
void show_config(char const * name)
{
    std::cout <<
    name << ":" << std::endl <<
    " tag: " << typeid( typename C::tag ).name() << std::endl <<
    " pointer_type: " << typeid( typename C::pointer_type ).name() << std::endl <<
    " constant_time_size: " << C::constant_time_size << std::endl <<
    std::endl;
}

int main()
{
    show_config< container_xyz< int, container_xyz_options< constant_time_size<false> >::type > >
                 ("container_xyz< int, container_xyz_options< constant_time_size<false> >::type >");

    show_config< container_xyz< int, container_xyz_options< offset_ptr_storage >::type > >
                 ("container_xyz< int, container_xyz_options< offset_ptr_storage >::type >");

    show_config< container_xyz< int, container_xyz_options< offset_ptr_storage, tag<int> >::type > >
                 ("container_xyz< int, container_xyz_options< offset_ptr_storage, tag<int> >::type >");

    return 0;
}


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk