EnumeratedString

Why EnumeratedString?

It is common for a property to be a string, the value of which must be one of several enumerated values. Programmatically, this is representing an enum object in C++, but based on a set of strings instead of integer-types. What would be ideal would be something like the following:

enum class StringPropertyOptions: std::string {option1="OptionOne", ... };

However, this is not allowed under C++.

The EnumeratedString objects allow for binding an enum or enum class to a vector of strings, allowing for much of the same behavior. This allows for easy-to-read if and switch statements, as well as easy conversions and assignments with strings from the allowed set. This further adds an additional layer of validation for string properties, in addition to the StringListValidator used in the property declaration.

How to use the EnumeratedString

First include the EnumeratedString.h header file.

This is a template class, and its three template parameters are the name of an enum type, a pointer to static vector of std::string objects, and an optional pointer to a statically defined std::function for comparing std::string objects. The last template parameter is defaulted to compareStrings which implements a case-sensitive string comparison. A predefined function for case-insensitive string comparison, compareStringsCaseInsensitive, is also provided as an option.

Below is an example. Consider the mantid algorithm BakeCake, which has a string property, CakeType. This algorithm only knows how to bake a few types of cakes. The allowed types of cake the user can set for this property are limited to “Lemon”, “Bundt”, and “Pound”.

The EnumeratedString should be setup as follows:

#include "MantidKernel/EnumeratedString.h"

namespace Mantid {

namespace {
enum class CakeTypeEnum {LEMON, BUNDT, POUND, enum_count};
const std::vector<std::string> cakeTypeNames {"Lemon", "Bundt", "Pound"};
// optional typedef
typedef EnumeratedString<CakeEnumType, &cakeTypeNames> CAKETYPE;
} // namespace

// ...

// initialize an object
EnumeratedString<CakeTypeEnum, &cakeTypeNames> cake1 = CakeTypeEnum::LEMON;
EnumeratedString<CakeTypeEnum, &cakeTypeNames> cake2 = "Lemon";

//init from the typedef
CAKETYPE cake3 = "Pound";

bool sameCake = (cake1==cake2); //sameCake = true, a Lemon cake is a Lemon cake
bool notSameCake = (cake1!=cake3); //notSameCake = true, a Lemon cake is not a Pound cake

Notice that the final element of the enum is called enum_count. This is mandatory. This element indicates the number of elements inside the enum, and used for verifying compatibility with the vector of strings. A compiler error will be triggered if this is not included.

Further, the enum must have elements in order from 0 to enum_count. That is, you CANNOT set them like so:

enum class CakeTypeEnum : char {LEMON='l', BUNDT='b', POUND='p', enum_count=3}; // NOT ALLOWED

as this will break validation features inside the class.

Notice the use of the reference operator, &cakeTypeNames, and not cakeTypeNames.

In the above code, a CAKETYPE object can be created either from a CakeTypeEnum, or from one of the strings in the cakeTypeNames array (either by the literal, or by accessig it in the array), or from another CAKETYPE object. The only assignment/comparison not directly possible is from CakeTypeEnum to one of the strings. Otherwise free conversion and comparison from CAKETYPE, CakeTypeEnum, and strings from cakeTypeNames is possible.

Example Use of EnumeratedString

An example of where this might be used inside an algorithm is shown below:

#include "MantidAlgorithms/BakeCake.h"
#include "MantidKernel/EnumeratedString.h"

namespace Mantid {

namespace {
enum class CakeTypeEnum {LEMON, BUNDT, POUND, enum_count};
const std::vector<std::string> cakeTypeNames {"Lemon", "Bundt", "Pound"};
typedef EnumeratedString<CakeEnumType, &cakeTypeNames> CAKETYPE;
} // namespace

namespace Algorithms {

void BakeCake::init() {
   // the StringListValidator is optional, but fails faster; the CAKETYPE cannot be set with string not in list
   declareProperty("CakeType", "Bundt", std::make_shared<Mantid::Kernel::StringListValidator>(cakeTypeNames),
      "Mandatory.  The kind of cake for algorithm to bake.");
}

void BakeCake::exec() {
   // this will assign cakeType from the string property
   CAKETYPE cakeType = getPropertyValue("CakeType");

   // logic can branch on cakeType comparing to the enum
   switch(cakeType){
   case CakeTypeEnum::LEMON:
      bakeLemonCake();
      break;
   case CakeTypeEnum::BUNDT:
      bakeBundtCake();
      break;
   case CakeTypeEnum::POUND:
      bakePoundCake();
      break;
   }

   getLemonsForCake("Bundt");
   getIngredientsForCake(cakeType);

   // other ways to compare
   if(cakeType == "Lemon"){
      g_log.information() << "Baking a lemon cake\n";
   }
   if(cakeType == CakeTypeEnum::BUNDT){
      g_log.information() << "Baking a bundt cake\n";
   }
   CAKETYPE poundCake = CakeTypeEnum::POUND;
   if(cakeType == poundCake){
      g_log.information() << "Baking a pound cake\n";
   }
}

void BakeCake::getLemonsForCake(CAKETYPE cakeType){
   if(cakeType == CakeTypeEnum::LEMON){
      g_log.information() << "Getting some lemons!\n";
   } else {
      g_log.information() << "I have no need for lemons.\n";
   }
}

void BakeCake::getIngredientsForCake(std::string cakeType){
   g_log.information() << "Retrieving ingredients for a " << cakeType << " cake!\n";
}

}// namespace Algorithms
}// namespace Mantid

This will easily handle branching logic on the basis of a set number of possible string values, using an enum to base the set of strings.

In the code examples above, if you don’t want to distinguish between names like “Lemon” and “LEMON”, you can define your CAKETYPE as case-insensitive:

#include "MantidKernel/EnumeratedString.h"

namespace Mantid {

namespace {
enum class CakeTypeEnum {LEMON, BUNDT, POUND, enum_count};
const std::vector<std::string> cakeTypeNames {"Lemon", "Bundt", "Pound"};
// optional typedef
typedef EnumeratedString<CakeEnumType, &cakeTypeNames, &compareStringsCaseInsensitive> CAKETYPE;
} // namespace

You can also provide your own string comparator like firstLetterComparator shown below:

#include "MantidKernel/EnumeratedString.h"

namespace Mantid {

namespace {
std::function<bool(const std::string &, const std::string &)> firstLetterComparator =
  [](const std::string &x, const std::string &y) { return x[0] == y[0]; };
enum class CakeTypeEnum {L, B, P, enum_count};
const std::vector<std::string> cakeTypeFirstLetters {"L", "B", "P"};
// optional typedef
typedef EnumeratedString<CakeEnumType, &cakeTypeFirstLetters, &firstLetterComparator> CAKETYPE;
} // namespace

in which case a “Lemon” cake will get the same enum value as a “Lime” cake.