4

I'm trying to make a struct with conditional members, that means, different members exists only with specific specializations. But, I want that this classes are fastest as possible. I've tried it in three differents ways:

Way 1:

 template<typename T, bool with_int = false>
 struct foo
 {
     template<typename... Args>
     foo(Args&&... args) : m_t(forward<Args>(args)...)
     {}

     T m_t;
 }

 template<typename T>
 struct foo<T, true>
 {
     template<typename... Args>
     foo(Args&&... args) : m_t(forward<Args>(args)...), m_id(0)
     {}

      T m_t;
      int m_id;
 };
  • Disadventage: repeated code for each specialization.

Way 2:

 template<typename T, bool with_int = false>
 struct foo
 {
     template<typename... Args>
     foo(Args&&... args) : m_t(forward<Args>(args)...)
     {}

     virtual ~foo() {}

     T m_t;
 }

 template<typename T>
 struct foo<T, false> : public foo<T>
 {
      using foo<T>::foo;

      int m_id = 0;
 };
  • Adventage: few code.
  • Disadventage: Use of vtables/inheritance/etc: more time in construction or access to members? But, in other way, I don't pretend to to use "references" to base class. What is it the real adventages or disadventages of this aproach?

Way 3

 using nil_type = void*;
 using zero_type = nil_type[0];

 template<typename T, bool with_int = false>
 struct foo
 {
    template<typename... Args, typename = typename enable_if<with_int>::type>
    foo(Args&&... args) : m_t(forward<Args>(args)...), m_int(0)
    {}

    template<typename... Args, typename = typename enable_if<!with_int>::type>
    foo(Args&&... args) : m_t(forward<Args>(args)...)
    {}        

    T m__t;
    typename conditional<with_int, int, zero_type>::type m_int;
 };
  • Ventages: Write code once; when with_int is false, field m_int has size 0 (almost with gcc 4.7.2).
  • Adventages: More use of templates (reduce readability) and I'm not sure about how compilers deal with member of size 0. I don't know indeed to what extent a size-0-field is dangerous or has sense. Repeated constructors, but perhaps this is avoidable.

What is the best approach or method?

ABu
  • 10,423
  • 6
  • 52
  • 103
  • OT, but I'm pretty sure names containing a double underscore are reserved for the compiler so you should go with `m_int` and `m_t`. – filmor Mar 18 '13 at 09:48
  • I think only members **beginning** with double underscore, but not containing it. – ABu Mar 18 '13 at 09:49
  • 5
    Nope, see the answer [here](http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier): `Each name that contains a double underscore (_ _) or begins with an underscore followed by an uppercase letter (2.11) is reserved to the implementation for any use.` – filmor Mar 18 '13 at 09:51
  • Note that you can't have a member of size 0, the compiler will still allocate a non-zero size for it. &Var1 == &Var2 if and only if Var1 and Var2 are the same variable. Any variable that has size 0 would violate that. – enobayram Mar 18 '13 at 09:56
  • Then, `sizeof` lies? `sizeof` said me `zero_type` has a size 0, `foo` size 4, and `foo` size 8. It is the same with `struct { char m[0] };`: `sizeof` returns 0 with it. – ABu Mar 18 '13 at 09:59
  • Still, the given equivalence has to hold. The compiler will just insert padding bytes in your struct to accomplish that. – filmor Mar 18 '13 at 10:00
  • You could use a tuple for a data member. `tuple` or `tuple` depending on that template parameter. – sellibitze Mar 18 '13 at 10:31
  • Arrays with size 0 are illegal. What you could use in 3rd approach is [Boost Compressed Pair](http://www.boost.org/doc/libs/1_53_0/libs/utility/compressed_pair.htm). – jrok Mar 18 '13 at 10:57

1 Answers1

5

Have you considered inheritance?

template< bool >
struct foo_int_base
{
  // stuff without the int
  void f(); // does not use m_id
};

template<>
struct foo_int_base< true >
{
  // stuff with the int
  int m_id = 0;
  void f(); // uses m_id
};

template< typename T, bool with_int = false >
struct foo : foo_int_base< with_int >
{
  // common stuff here
};
Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
  • Use of inheritance is contempled in my second aproach. Read it please and my questions just below. – ABu Mar 18 '13 at 09:54
  • 1
    Your approach is different, is uses `virtual` (which has a cost) and it tries to derive the int-case from the non-int-case which will IMHO make things more complicated and provide no clear separation. But it's just my suggestion, I can't know if this could work for you. – Daniel Frey Mar 18 '13 at 10:09
  • This is the correct solution. Note that the _additional_ size of `foo_int_base` is 0 (certainly in optimized builds). Unlike members, base class subobjects can have zero size. – MSalters Mar 18 '13 at 12:10