many of nowaday c++ code tend template-loaded in greatest extent. libraries: stl, boost.spirit, boost.mpl, etc among many other. encourages users declare functional objects in form struct s { /* presence of non-virtual member functions , operators, absense of non-static data members or non-empty base classes */ }; s const s{};
. of them stateless (i.e. static_assert(std::is_empty< s >{});
holds). of them, odr-used, regardless of theirs emptyness data
section of file growth 1 byte (sizeof(s) == 1
empty type s
because addresses consequentially allocated objects should different). in simple grammars of boost.spirit there plenty of such odr-used empty classes. absolutely of no sense keep space them.
i tried test clang
on coliru using following code (-ofast
):
#include <utility> #include <type_traits> #include <cstdlib> #include <cassert> template< std::size_t index > struct s {}; namespace { template< std::size_t index > s< index > value = {}; } template< typename lhs, typename rhs > std::ptrdiff_t diff(lhs & l, rhs & r) { return (static_cast< char * >(static_cast< void * >(&r)) - static_cast< char * >(static_cast< void * >(&l))); } template< std::size_t base, std::size_t ...indices > std::ptrdiff_t bss_check(std::index_sequence< indices... >) { return (diff(value< (base + indices) >, value< (base + indices + 1) >) + ...); } template< std::size_t size, std::size_t base > bool enumerate() { return (bss_check< base >(std::make_index_sequence< size >{}) + 1 == size); } template< std::size_t size, std::size_t ...bases > bool expand(std::index_sequence< bases... >) { return (enumerate< size, (bases * size) >() && ...); } template< std::size_t size = 100, std::size_t count = size > bool check() { return expand< size >(std::make_index_sequence< count >{}); } int main() { static_assert(std::is_empty< s< 0 > >{}); assert((check< dim >())); return exit_success; }
and result (output of size
utility dim == 100
, i.e. 100 * 100 classes):
text data bss dec hex filename 112724 10612 4 123340 1e1cc ./a.out
if change signature of diff(lhs & l, rhs & r)
diff(lhs l, rhs r)
in order suppress odr-using, result is:
text data bss dec hex filename 69140 608 8 69756 1107c ./a.out
is equal (data
section of interest) case of simple commenting of assert((check< dim >()));
line (major part of text
section predictable dce-optimized out):
text data bss dec hex filename 1451 600 8 2059 80b ./a.out
hence conclude there no optimization odr-used empty classes.
for explicitly specified template parameters there possibility use simple typefilter:
template< typename type > using ref_or_value = std::conditional_t< std::is_empty< std::decay_t< type > >{}, std::decay_t< type >, type && >;
but there no simple workaround deduced template types @ mind.
is there implied above optimization in modern compilers? if yes, how enable it? if no, there technique achieve desired behaviour @ moment?
i know somtimes addresses of objects mutters much, not case in described above situation.
i think attribute variable or type (e.g. [[immaterial]]
) handy. maybe such attribute (used classes) should deny possibility address of instances of attributed classes (compile-time hard error) or address-of operator &
shoud return non-sense value (implementation-defined).
c++17 adding inline variables address of these issues explained in n4424. explains workarounds. global function objects can define them this:
// sum function object struct sum_f { template<class t, class u> auto operator()(t x, u y) const { return x+y; } }; template<class t> struct static_const_storage { static constexpr t value = t(); }; template<class t> constexpr t static_const_storage<t>::value; template<class t> constexpr const t& static_const() { return static_const_storage<t>::value; } static constexpr auto& sum = static_const<sum_f>();
this makes sum
function object unique across translation units thereby avoiding bloat , odr violations. however, workaround doesn't work template variables, , best avoid them(if concerned executable bloat) until inline variables in c++17.
Comments
Post a Comment