eddi eddi - 2 months ago 9
C++ Question

How can I make this more robust, given that there is no reflection in C++?

I have the following class:

template <typename T>
T get_value(std::string name, T defaultValue)
{
// read values from config file and assign to var; if not found use defaultValue
return defaultValue;
}

class A {
public:
A()
: myvar1_(get_value("myvar1", 0))
, myvar2_(get_value("myvar2", 1.5))
{
}

int myvar1_;
double myvar2_;

std::string asString() const {
std::stringstream str;
str << "myvar1 = " << myvar1_
<< ", myvar2 = " << myvar2_;
return str.str();
}
private:
// other things exist here, unrelated to myvar1/2
};


Where
get_value
is a function that reads the values of the variables from some config file, and if not found uses the default value. I also have
myvar1_
and
myvar2_
as public member variables, because they get accessed directly, and I'd like to keep that feature and do not want to add a potential function hop.

Now, you can see that I have typed
myvar1
or
myvar1_
in quite a few different places, and I'd like to make this more robust, so that I can type, somewhere somehow,
myvar1_, "myvar1", 0
once (as opposed to having typed
myvar1_
3 times and
"myvar1"
twice), and automagically get the above functions called and values filled. I have a lot of variables and they get added or removed fairly frequently, and sometimes I forget to initialize them, or mistype the string name in
set_value
, or forget to add a new variable to
asString
.

Is that possible to do? I'd appreciate any hints.

Answer

I ended up with a macro solution, heavily borrowing from a different SO answer:

#define EVAL0(...) __VA_ARGS__
#define EVAL1(...) EVAL0 (EVAL0 (EVAL0 (__VA_ARGS__)))
#define EVAL2(...) EVAL1 (EVAL1 (EVAL1 (__VA_ARGS__)))
#define EVAL3(...) EVAL2 (EVAL2 (EVAL2 (__VA_ARGS__)))
#define EVAL4(...) EVAL3 (EVAL3 (EVAL3 (__VA_ARGS__)))
#define EVAL(...)  EVAL4 (EVAL4 (EVAL4 (__VA_ARGS__)))

#define MAP_END(...)
#define MAP_OUT

#define MAP_GET_END0() 0, MAP_END
#define MAP_GET_END1(...) 0

#define GET_MACRO(_0, _1, _2, _3, _4, NAME, ...) NAME
#define MAP_GET_END(...) GET_MACRO(_0, ##__VA_ARGS__, MAP_GET_END1, MAP_GET_END1, MAP_GET_END1, MAP_GET_END1, MAP_GET_END0)(__VA_ARGS__)

#define MAP_NEXT0(item, next, ...) next MAP_OUT
#define MAP_NEXT1(item, next) MAP_NEXT0 (item, next, 0)
#define MAP_NEXT(item, next)  MAP_NEXT1 (MAP_GET_END item, next)

#define MAP0(f, x, peek, ...) f(x) MAP_NEXT (peek, MAP1) (f, peek, __VA_ARGS__)
#define MAP1(f, x, peek, ...) f(x) MAP_NEXT (peek, MAP0) (f, peek, __VA_ARGS__)

#define MAP(f, ...) EVAL (MAP1 (f, __VA_ARGS__, (), 0))


#define DEFINE_VARS_T(TYPE, NAME, DEFAULT_VALUE) \
  TYPE NAME##_;
#define DEFINE_VARS(TUPLE) DEFINE_VARS_T TUPLE

#define CONSTRUCT_VARS_T(TYPE, NAME, DEFAULT_VALUE) \
  NAME##_ = get_value(#NAME, DEFAULT_VALUE);
#define CONSTRUCT_VARS(TUPLE) CONSTRUCT_VARS_T TUPLE

#define PRINT_VARS_T(TYPE, NAME, DEFAULT_VALUE) \
  << #NAME << " = " << NAME##_ << ", "
#define PRINT_VARS(TUPLE) PRINT_VARS_T TUPLE

#define CONFIG_VARS(...) \
  MAP(DEFINE_VARS, __VA_ARGS__) \
  A() { \
    MAP(CONSTRUCT_VARS, __VA_ARGS__) \
  } \
  std::string asString() const { \
    std::stringstream str; \
    str MAP(PRINT_VARS, __VA_ARGS__); \
    return str.str(); \
  }

template <typename T>
T get_value(std::string name, T defaultValue)
{
  // read values from config file and assign to var
  return defaultValue;
}

class A {
public:
  CONFIG_VARS
  (
    (int, myvar1, 0),
    (double, myvar2, 1.5),
    (std::string, myvar3, "what")
  )

private:
  // other things exist here, unrelated to myvar1/2
};
Comments