3
\$\begingroup\$

Edit: Disclaimer: I am new to templates and traits. I apologize in advance if this is a bad question.

I am refactoring two similar classes with small differences strewn all over them into one template class (or I am trying to anyway). This is something I just wrote. (I hope I didn't make any mistakes renaming everything.)

// declaration - empty
template <typename SETTINGS>
struct SettingsTraits;

// specialization for type CFoo
template <>
struct SettingsTraits<CFoo>
{
  typedef CFooRelated RelatedType;
  typedef CAttribute<CFooRelated > & (CDataSource::*AttrGetter)();
  static CAttribute<CFooRelated > & (CDataSource::*GetAttr)();
};

SettingsTraits<CFoo>::AttrGetter SettingsTraits<CFoo>::GetAttr = &CDataSource::GetFooAttr;

// specialization for type CBar    
template <>
struct SettingsTraits<CBar>
{
  typedef CBarRelated RelatedType;
  typedef CAttribute<CBarRelated> & (CDataSource::*AttrGetter)();
  static CAttribute<CBarRelated> & (CDataSource::*GetAttr)();
};

SettingsTraits<CBar>::AttrGetter SettingsTraits<CBar>::GetAttr = &CDataSource::GetBarAttr;

So that I can write something like this

CAttribute<SettingsTraits<SETTINGS>::RelatedType> attr = (datasource.*SettingsTraits<SETTINGS>::GetAttr)();

But now I am wondering if I shouldn't just have created two different structs and passed those as template parameters instead.

I imagine the calling code would end up looking like this:

CAttribute<FooTraits::RelatedType> attr = FooTraits::GetAttr(datasource);

So probably somewhat cleaner but I haven't tried it so I don't know if it will actually work. (I should probably replace the function pointer with a simple one line function that calls the appropriate function for each type in any case.)

Which alternative should I chose? What are the benefits/drawbacks of each?

Does it even make sense to have functions in traits? Should I maybe just use free template functions instead?

\$\endgroup\$
2
\$\begingroup\$

Yes, the std way of doing things in C++ would be to provide static member functions of your traits type. Consider:

// declaration - empty
template <typename SETTINGS>
struct SettingsTraits;

// specialization for type CFoo
template <>
struct SettingsTraits<CFoo>
{
  typedef CFooRelated RelatedType;
  static CAttribute<RelatedType>& GetAttrFrom(CDataSource& ds) { return ds.GetFooAttr(); }
};

// specialization for type CBar    
template <>
struct SettingsTraits<CBar>
{
  typedef CBarRelated RelatedType;
  static CAttribute<RelatedType>& GetAttrFrom(CDataSource& ds) { return ds.GetBarAttr(); }
};

typedef SettingsTraits<SETTINGS> TRAITS;
CAttribute<TRAITS::RelatedType> attr = TRAITS::GetAttrFrom(datasource);

Notice that this way of doing things doesn't involve global objects or values at all; it's all done entirely within the type system. The compiler can look at the line TRAITS::GetAttrFrom(datasource) and statically know that it's equivalent to datasource.GetFooAttr(), which means the whole thing compiles down to a single call instruction. Your old code would have required a trip to memory in order to load the value of the pointer SettingsTraits<CFoo>::GetAttr.

Rule of thumb: you should never need to construct a type-traits object at all. If you do, you're probably doing it wrong.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.