Compile Time Meta Programming with String Literals
I am writing code that I could really do with simple compile-time metaprogramming. It is common practice to use empty-struct tags as compile-time characters. I need to decorate the tags with some runtime config elements. static variables seem like the only way (to enable metaprogramming), however static variables require global declarations. to a side step, this suggestion by Scott Myers (from the 3rd edition of Effective C ++), about the sequence of initializing static variables by declaring them inside a function and not as class variables, came to mind.
So I came up with the following code, my hypothesis is that it will allow me to have a compile-time character with string literals that can be used at runtime. I am not missing anything I hope for, and that it will work correctly if I fill in the runtime fields before I initialize the dependent template classes ?.
#include <string>
template<class Instance>
class TheBestThing {
public:
static void set_name(const char * name_in) {
get_name() = std::string(name_in);
}
static void set_fs_location(const char * fs_location_in) {
get_fs_location() = std::string(fs_location_in);
}
static std::string & get_fs_location() {
static std::string fs_location;
return fs_location;
}
static std::string & get_name() {
static std::string name;
return name;
}
};
struct tag {};
typedef TheBestThing<tag> tbt;
int main()
{
tbt::set_name("xyz");
tbt::set_fs_location("/etc/lala");
ImportantObject<tbt> SinceSlicedBread;
}
edit Made by community wiki.
a source to share
I finally figured out what the problem was ... and your solution doesn't solve much, if any.
The purpose of using a local static variable is to provide initialization on first use, thus being safe from the Fiasco Initialization Order (it does not solve the Fiasco Order Destruction by the way).
But with your construct, if you effectively prevent crash
, you are still not preventing the problem of using the variable before using it.
ImportantObject<tbt> SinceSliceBread; // using an empty string
tbt::set_name("xyz");
Compare with the following usage:
std::string& tbt::get_name() { static std::string MName = "xyz"; return MName; }
name
Not only is created here , but also initialized on first use. What's the point of using a non-initialized name?
Ok, now that we know your solution isn't working, think a little. We'd actually like to automate this:
struct tag
{
static const std::string& get_name();
static const std::string& get_fs_location();
};
(possibly with some accessories to change them)
My first (and simple) solution would be to use the (bouh not typesafe) macro:
#define DEFINE_NEW_TAG(Tag_, Name_, FsLocation_) \
struct Tag_ \
{ \
static const std::string& get_name() { \
static const std::string name = #Name_; \
return name; \
} \
static const std::string& get_fs_location() { \
static const std::string fs_location = #FsLocation_; \
return fs_location; \
} \
};
Another solution in your case could be to use boost::optional
to detect that the value has not been initialized yet and defer the initialization of the values that depend on it.
a source to share