|
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