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:
S::x
and S::y
that conditionally exist based on the contents of the template parameter packS::Fields
that is obtained by transforming the contents of the template parameter packThe struct T
behaves more like what I am hoping to achieve:
T::x
and T::y
conditionally exist based on the template parametersT::Fields
returns a value that depends on the template parametersT
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:
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;
}
};