Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using template instead of switch

Tags:

c++

templates

I want to perform a set of tests in my code that are similar but change only depending on a parameter.

I could write this using a switch statement:

bool doTest(EnumSensorFamily family, const StructSensorProposal& proposed)
{
  switch (family)
  {
  case FAM1:
    return (ExpectedFam1 == proposed.Fam1SensorId);
    break;
  case FAM2:
    return (ExpectedFam2 == proposed.Fam2SensorId);
    break;
  case FAM3:
    return (ExpectedFam3 == proposed.Fam3SensorId);
    break;
  default:
    ERROR ("Unexpected family");
    return false;
  }
}

I was thinking of doing this with template specialisations

template <EnumSensorFamily family>
bool doTest(const StructSensorProposal& proposed);

template<>
bool doTest<FAM1> (const StructSensorProposal& proposed)
{
  return (ExpectedFam1 == proposed.Fam1SensorId);
}

template<>
bool doTest<FAM2> (const StructSensorProposal& proposed)
{
  return (ExpectedFam2 == proposed.Fam2SensorId);
}

template<>
bool doTest<FAM3> (const StructSensorProposal& proposed)
{
  return (ExpectedFam3 == proposed.Fam3SensorId);
}

Is there any benefit of doing this apart from avoiding a switch statement containing near identical cases?

Ideally I would like to be able to write single method to reduce the maintenance overhead.

thanks

like image 563
DanS Avatar asked Apr 13 '11 13:04

DanS


4 Answers

Building off Andrew's answer...

Note that the EnumSensorFamily family must be known at compile time. If it is not known until run time, then you'll have to write a switch to choose the template, putting you back where you started.

Another way to do this is with the Traits pattern:

template <EnumSensorFamily family>
struct SensorTraits;

template <>
struct SensorTraits<FAM1>
{
    const EnumSensorFamily kFamilyID = ExpectedFam1;
};

template <>
struct SensorTraits<FAM2>
{
    const EnumSensorFamily kFamilyID = ExpectedFam2;
};

template <>
struct SensorTraits<FAM3>
{
    const EnumSensorFamily kFamilyID = ExpectedFam3;
};

template <EnumSensorFamily family>
bool doTest(const StructSensorProposal& proposed)
{
  return (SensorTraits<family>::kFamilyID == proposed.Fam1SensorId);
}

If you try to use doTest with a sensor family that lacks a traits specialization, you get a compile error. Also note that you never instantiate a traits object, you just use its definitions.

This lets you reuse constants, typedefs, whatever in several functions. Additionally, adding a new family does not involve combing through all the code looking for every switch statement that cares. All you have to do is create a new SensorTraits specialization.

EDIT: You can make the field dependent on the sensor family with a pointer to member:

template <>
struct SensorTraits<FAM1>
{
    const EnumSensorFamily kFamilyID = ExpectedFam1;
    int StructSensorProposal::*proposalField = &StructSensorProposal::fam1field;
};

// ...

template <EnumSensorFamily family>
int getProposedField(const StructSensorProposal& proposed)
{
    return proposed.*SensorTraits<family>::proposalField;
}

You can also put in, say, a typedef for the sensor's data type:

template <>
struct SensorTraits<FAM1>
{
    const EnumSensorFamily kFamilyID = ExpectedFam1;
    typedef uint16_t data_type;
    data_type StructSensorProposal::*proposalField = &StructSensorProposal::fam1field;
};

// ...

template <EnumSensorFamily family>
SensorTraits<family>::data_type getProposedField(const StructSensorProposal& proposed)
{
    return proposed.*SensorTraits<family>::proposalField;
}

I haven't tested these; you might need a const or static in there.

like image 151
Mike DeSimone Avatar answered Nov 20 '22 02:11

Mike DeSimone


The benefit is a very small potential performance improvement if the compiler fails to optimize the switch properly (i.e. if it doesn't generate the same code as for the template solution, which is doable for a modern, inlining compiler). Of course only if family is a compile-time constant - otherwise templates are not applicable and the best optimization that the compiler could find for the switch would be a computed jump or a jump table.

If your code always looks like return (ExpectedFamN == proposed.FamNSensorId);, I'd rather use arrays for the expected values and the sensor IDs and index those based on the family.

like image 26
Alexander Gessler Avatar answered Nov 20 '22 01:11

Alexander Gessler


It is impossible to use templates in the following case:

const  EnumSensorFamily familyCompileTime = FAM3; // Compile time constant
EnumSensorFamily family = GetFimilyInRunTime(); // Run time variable
doTest1(family, proposed); // ok
doTest2<family>(proposed); // error;
doTest2<familyCompileTime >(proposed); // OK;
like image 2
Alexey Malistov Avatar answered Nov 20 '22 01:11

Alexey Malistov


Well in a way... you are moving processing away from run time to compile time. The compiler will create the functions and use them accordingly instead of at run time having to go through the switch statement.

Also, it will result in cleaner code.

like image 1
poy Avatar answered Nov 20 '22 01:11

poy