2

So I have a nontemplate base class which contains "default" settings for it's methods. Then I'm trying to use inheritance with a templated class. Here is a snippet of example code for illustration.

// enums for as a template selector
enum class version
{
    ver1,
    ver2,
    ver3
};

// Base class with fabricated methods
struct base
{
    virtual void propertyOne()
    {
        // some default action
    }

    virtual void propertyTwo()
    {
        // some default action
    }
};  



// derived class
template <version V>
struct derived : public base
{
    virtual void propertyOne()
    {
        helper< One, V >();
    }

    virtual void propertyTwo()
    {
        helper< Two, V >();
    }
}

I'm using a helper function to perform a "generic" algorithm on different "fields" which are used in class traits.

For example: A field is something similar to this

struct field
{
    int thingone;

    constexpr field(int i):thingone(i){}
};

In c++ 11, in order to give field instances external linkage I wrapped them as static members of another structure (c++14 relaxes these rules, oh well). The whole reason I'm doing this is because I need constant expression values from it (e.g. member variable thingone is needed as a template params of another method which requires it to be a constant expression).

struct fields
{
    static constexpr field One{1};
    static constexpr field Two{2};
};

// defining trait class from structure above
template< const field& T, revision R >
class fieldTraits;

// sample fieldTrait definitions for illustrative purposes

template< >
class fieldTraits< fields::One, revision::ver3>
{
    public:
        // Let's say I have common field names
        // with different constants that I want to plug
        // into the "helper" algorithm
        static constexpr size_t field_val = 1; 
};

template< >
class fieldTraits< fields::Two, revision::ver1>
{
    public:
        // Let's say I have common field names
        // with different constants that I want to plug
        // into the "helper" algorithm
        static constexpr size_t field_val = 1; 
};  

// Main guts of the class methods above
template< const field& F, revision R, typename TT = traitClass<F,R> >
void helper()
{
    // Let's pretend I'm doing something useful with that data
    std::cout << F.thingone << std::endl;
    std::cout << TT::field_val << std::endl;
}

The problem I hit is trying to instantiate for example

derived<revision::rev1> l_derived;

Since I only defined the trait class for ver3, I can't instantiate the class without defining the trait class for ver1, and ver2 explicitly. But if the traits classes are exactly the same for, say, ver1 - ver 3, is any kind of enable_if condition I had to make this template class valid for all revs <= ver3 ?

I couldn't find anything in the traits_type header which mainly offers compile time "type" checks such as std::is_same, etc.

I know one option is to copy-paste the trait classes for ver1-ver3 but that seemed to be redundant since I wanted to avoid copy-pasting repetitive code.

Another option is to create different classes for each revision and leverage dynamic polymorphism where I can define a class for each revision. And then I only need to include trait revisions changes where they are needed. For example,

class derived_ver1 : public base
{
    virtual void propertyOne()
    {
        helper< fields::One, revision::ver1 >();
    }

    virtual void propertyTwo()
    {
        helper< fields::Two, revision::ver1 >();
    }
};

class derived_ver2 : public derived:ver1
{
    virtual void propertyTwo()
    {
        helper< fields::Two, revision::ver2 >();
    }
};

class derived_ver3 : public derived:ver2
{
    virtual void propertyTwo()
    {
        helper< Two, revision::ver3 >();
    }
};

In this example propertyOne() could reuse the traits class from revision 1 for revision2 and revision 3 because it didn't change past revision 1 (and avoids copy-paste of traits).

Is there a better design I could approach?

In summary: is there a way to use my original template inheritance and use some template feature (e.g. std::enable_if) to reuse a trait class for undefined revisions. Instead of explicitly definining a trait for each revision (which leads to copy-paste).

Or is the second approach using dynamic polymorphism a better way to do this (which I read, has increased costs of vtable lookup that comes w/it)?

max66
  • 65,235
  • 10
  • 71
  • 111
Andre Marin
  • 540
  • 1
  • 6
  • 16
  • Your goal isn't entirely clear. You want `fieldTraits` and/or the use of `TT` within `helper` to fall back to a different result when you don't declare/define the member that would most directly give the property? Based on previous `revision` values and/or some overall default? Maybe it would help if you gave a table of what result you want where you wrote `TT::field_val` for all nine specializations? – aschepler May 07 '18 at 22:55
  • I modified my question, I want the helper to fall_back to a base if possible. For example to use revision 3 trait for an undefined revision 2 or revision 1 trait class. I think the answer is no...you can't do that but wanted to verify. I don't think the field_val value for these specializations matter for my question? – Andre Marin May 08 '18 at 01:23
  • Sorry, I still don't understand. What do you wish `TT::field_val` would do in `helper()` and in `helper()`, and why? (You sometimes have `version` and sometimes `revision`, but it looks like maybe these are supposed to be the same?) – aschepler May 08 '18 at 12:49

1 Answers1

0

I find very difficult to understand what do you want; please, revise you question to check details (version and revision are the same? fieldTraits and traitsClass are the same?).

Anyway, if I understand correctly, you want define a specialization for

template <const field& T, revision R>
class fieldTraits;

when R is val1 or val2 or val3 and, maybe, other specializations for single following values.

Supposing you have four revisions

enum class revision { ver1, ver2, ver3, ver4 };

You can declare fieldTraits (as a struct, to make it shorter) adding a bool default template parameter that say if R <= revision::ver3

template <field const & T, revision R, bool = (R <= revision::ver3)>
struct fieldTraits;

Now you can develop a specialization for ver1, ver2 and ver3

template <field const & T, revision R>
struct fieldTraits<T, R, true>
 { static constexpr size_t field_val = 1; };

and a specialization for ver4

template <field const & T>
struct fieldTraits<T, revision::ver4>
 { static constexpr size_t field_val = 2; };

The following is a simplified but full example

#include <iostream>

enum class revision { ver1, ver2, ver3, ver4 };

struct field
 {
   int thingone;

   constexpr field (int i) : thingone(i)
    { }
 };

template <field const & T, revision R, bool = (R <= revision::ver3)>
struct fieldTraits;

// version ver1, ver2, ver3 cases
template <field const & T, revision R>
struct fieldTraits<T, R, true>
 { static constexpr size_t field_val = 1; };

// version ver4 case
template <field const & T>
struct fieldTraits<T, revision::ver4>
 { static constexpr size_t field_val = 2; };

static constexpr field f{1};

int main ()
 {    
   std::cout << "ver1: " << fieldTraits<f, revision::ver1>::field_val
      << std::endl;
   std::cout << "ver2: " << fieldTraits<f, revision::ver2>::field_val
      << std::endl;
   std::cout << "ver3: " << fieldTraits<f, revision::ver3>::field_val
      << std::endl;
   std::cout << "ver4: " << fieldTraits<f, revision::ver4>::field_val
      << std::endl;
 }

that print

ver1: 1
ver2: 1
ver3: 1
ver4: 2

-- EDIT --

Following an OP's comment, I propose a little different solution

template <field const & T, revision R, typename = std::true_type>
struct fieldTraits;

// version ver1, ver2, ver3 cases
template <field const & T, revision R>
struct fieldTraits<T, R, std::integral_constant<bool, (R <= revision::ver3)>>
 { static constexpr size_t field_val = 1; };

// version ver4 case
template <field const & T>
struct fieldTraits<T, revision::ver4>
 { static constexpr size_t field_val = 2; };
max66
  • 65,235
  • 10
  • 71
  • 111
  • That really helps thx. How different is this to enable_if? Does this effectively disable traits? – Andre Marin May 08 '18 at 15:44
  • @user1945925 - No, nothing in SFINAE disabled. It's only used specialization: calling `fieldTraits::field_val` is activated the third (defaulted) value as `true` (because is true that `ver1 <= ver3`) so the `fieldTraits` specialization match. If you try calling `fieldTraits::field_val`, you should get an error because no specialization match and the main version is declared but not defined. – max66 May 08 '18 at 16:17
  • I was trying to modify your example by making the declaration as `template< const field_t& F, rev R, bool B = true > class readerTraits;` and then tried creating a partial class specialization as `template class readerTraits< fields::One, R, (R <= rev::V1_1) >` however I'm getting a `error: template argument ‘(R <= V1_1)’ involves template parameter(s)`. Because R is non-type template param ? https://stackoverflow.com/questions/5978617/why-is-it-disallowed-for-partial-specialization-in-a-non-type-argument-to-use-ne – Andre Marin May 08 '18 at 16:25
  • @user1945925 - exactly; but you've given me an idea for a little different solution... – max66 May 08 '18 at 16:32
  • @user1945925 - answer modified adding solution that follows your idea (more flexible, IMHO) – max66 May 08 '18 at 16:38
  • Presumably the real `fieldTraits` will have multiple members. If some of these need to split differently on revisions, note you're allowed to specialize members of a class template individually, not just the entire class. – aschepler May 08 '18 at 23:24
  • @aschepler - frankly, I don't understand what the OP exactly needs. If he needs specialization based over single members, I suppose he can do it thought inheritance of different base classes and differentiate the specializations of base classes. – max66 May 09 '18 at 00:49
  • @max66 What I was originally looking for a solution where I could define `template struct fieldTraits;`, then create `template struct fieldTraits> { static constexpr size_t field_val = 1; };` and then cover all higher revs as `template struct fieldTraits= revision::ver3)>> { static constexpr size_t field_val = 2; };`. But that didn't work due to compile issues. Had to specialize ver1 and ver2 – Andre Marin Jul 13 '18 at 02:59