Reputation: 49448
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.
Upvotes: 1
Views: 134
Reputation: 49448
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
};
Upvotes: 0
Reputation: 24249
Option 1: A DSL that generates your code from definitions.
Option 2: Use a class facade over the config lookup, backed presumably by access to your config class, and combine with C++11's member initialization. This will reduce the number of times you have to repeat a variable, which can then be hidden with a little macroing if you're up for that.
#include <iostream>
template<typename T>
class DV {
T value_;
public:
DV(const char* name, const T& defaultValue)
{
// read values from config file and assign to var; if not found use defaultValue
value_ = defaultValue;
}
operator const T& () const { return value_; }
};
using DVInt = DV<int>;
using DVStr = DV<const char*>;
struct A {
int i_ = DVInt("i", 42);
const char* str_ = DVStr("str", "hello");
A() = default;
};
int main() {
A a;
std::cout << a.i_ << ", " << a.str_ << "\n";
}
Demo: http://ideone.com/RAyKwI
-- Edit --
Reducing to one instance with a macro.
Instead of the using
statements:
#include <iostream>
template<typename T>
class DV {
T value_;
public:
DV(const char* name, const T& defaultValue)
{
// read values from config file and assign to var; if not found use defaultValue
value_ = defaultValue;
}
operator const T& () const { return value_; }
};
#define CONCAT(x, y) x ## y
#define DVDecl(T, name, def) T CONCAT(name, _) = DV<T>(#name, def)
#define DVInt(name, def) DVDecl(int, name, def)
#define DVCStr(name, def) DVDecl(const char*, name, def)
struct A {
DVInt(myvar1, 42);
DVCStr(myvar2, "hello");
A() = default;
};
int main() {
A a;
std::cout << a.myvar1_ << ", " << a.myvar2_ << "\n";
}
This doesn't eliminate your need to manually add them to asString
, though.
Upvotes: 3