c++templatesvariadic-templatesdatamember

C++ class template with conditional data members


Consider the following C++ code.

#include <iostream>
#include <set>
#include <string>

enum class Field { kX, kY };

std::string ToString(const Field f) {
  switch (f) {
  case Field::kX:
    return "x";
  case Field::kY:
    return "y";
  default:
    return "?";
  }
}

std::set<std::string> FieldStrings(const bool has_x, const bool has_y) {
  std::set<std::string> field_strings;
  if (has_x) {
    field_strings.insert(ToString(Field::kX));
  }
  if (has_y) {
    field_strings.insert(ToString(Field::kY));
  }
  return field_strings;
}

template <Field... Args> struct S {
  int x = 0; // Should be present if and only if `kX` in `Args`.
  int y = 0; // Should be present if and only if `kY` in `Args`.

  // Should return `ToString` called on all of the `Field`s in `Args`.
  static const std::set<std::string> Fields() {
    static const std::set<std::string> kFields;
    return kFields;
  }
};

template <bool HasX, bool HasY> struct T {
  // Returns the fields that are available in the struct.
  static const std::set<std::string> &Fields() {
    static const std::set<std::string> kFields;
    return kFields;
  }
};
template <> struct T<false, true> {
  int y = 0;
  static const std::set<std::string> &Fields() {
    static const std::set<std::string> kFields = FieldStrings(false, true);
    return kFields;
  }
};
template <> struct T<true, false> {
  int x = 0;
  static const std::set<std::string> &Fields() {
    static const std::set<std::string> kFields = FieldStrings(true, false);
    return kFields;
  }
};
template <> struct T<true, true> {
  int x = 0;
  int y = 0;
  static const std::set<std::string> &Fields() {
    static const std::set<std::string> kFields = FieldStrings(true, true);
    return kFields;
  }
};

The struct S sketches out what I am hoping to achieve:

The struct T behaves more like what I am hoping to achieve:

T does not behave exactly like what I want because there are separate bool parameters for the fields rather than a pack of Field enums. More importantly, the implementation of T is not scalable: the number of specializations increases exponentially in the number of fields. It is not too bad to write out all the overloads when there are two fields, but it becomes a huge burden if there are 10 fields.

Is there any way to implement S? The key requirements are:


Solution

  • Specialization of the "Leaf" and inherit from those leaves does the job:

    template <Field field> struct FieldStorage;
    
    template <>
    struct FieldStorage<Field::kX>
    {
        int x = 0;
    
        static constexpr const char* name = "x"; // ToString switch no longer needed
    };
    
    template <>
    struct FieldStorage<Field::kY>
    {
        int y = 0;
    
        static constexpr const char* name = "y";
    };
    
    template<Field... Fs>
    struct S : FieldStorage<Fs>...
    {
        static const std::set<std::string>& Fields() {
            static const std::set<std::string> kFields{FieldStorage<Fs>::name...};
            return kFields;
        }
    };
    

    Demo