Reputation: 4674
We have a rather large project that defines static const std::string
s in several places to be used as parameter names; some of them need to be concatenated during static initialization:
foo.h:
struct Foo {
static const std::string ParamSuffix;
};
foo.cpp:
const std::string Foo::ParamSuffix = ".suffix";
bar.h:
struct Bar {
static const std::string ParamPrefix;
};
bar.cpp:
const std::string Bar::ParamPrefix = "prefix";
baz.h:
struct Baz {
static const std::string ParamName;
};
baz.cpp:
const std::string Baz::ParamName = Bar::ParamPrefix + Foo::ParamSuffix;
The problem is obviously the "static initialization fiasco" as it is undefined in which order the static const
members are initialized.
I dislike the usual solution, i.e. replace all these variables by functions, because
I currently cannot use C++11, which would make things easier with its constexpr
feature (I think).
The question is: Is there any trick that would allow me to concatenate static const std::string
s (or wrapper objects or whatever) to initialize another static const std::string
?
Upvotes: 3
Views: 1591
Reputation: 106196
When the text is not dependent on another variable, you might consider changing the type from const std::string
to a const char
array ala const char param_prefix[] = "prefix";
- there may well be less forced change to client usage, but that could hide new temporary std::string
s having to be (or unintentionally being) created each time they're used.
Check your system, but you can almost certainly safely use those const char
variables in the construction of other std::string
constants - but you're out of luck for necessarily-std::string
dependencies on other std::string
s.
If it's too hard to untangle the existing values into can-be-const
-char
and std::string
s that only depend on const
-char
s, or that just doesn't cover enough of your cases, then wholesale modifications to every constant are worth pursuing.
The appeal of wrappers is that you can keep the type and semantics of usage the same. To do that, you'll need to replace all these runtime-initialised strings with your own type that coordinates two central lists: - initialised objects, and waiting objects. An observer pattern is appropriate, or you could e.g. register a list of callbacks and complete initialisation by looping in main()
calling them until they all indicate success: if at least some of the object initialisation is known to be static then that could allow one object to test another's initialisation status, which would avoid having to have constructors register their completion.
If it's possible with the help of smarter source code modifying tools (e.g. awk) - it may be better to simply change the constants to be cleanly returned by functions.
Upvotes: 2
Reputation: 27375
The question is: Is there any trick that would allow me to concatenate static const std::strings (or wrapper objects or whatever) to initialize another static const std::string?
No trivial ones, other than the one you dislike: create functions for the static strings.
There are non-trivial alternatives though (e.g. replace all your hard-coded strings with a string container/string map and load the map on application start-up).
My recommendation though would be to go with static functions (the solution you rejected).
Upvotes: 3
Reputation: 153955
Why do you think that std::string const
is in any way special? (hint: it is not) Even if you could use C++11's constexpr
it wouldn't help because you can't make std:string
s constant expressions (they may need to allocate memory which isn't a viable constant expression). You could deal with a string-like class used for constexpr
that could be made to convert to std::string
but I'm not sure if these could be concatenated to produce a new constexpr
.
What you maybe able to do is to extract those strings you need to use as part of a concatenation and provide their value as macro. String literals (which have type char const[N]
for a suitable N
) can be concatenated by simply placing them next to each other:
// foo.h
#define FOO_PARAM_SUFFIX ".suffix"
struct Foo {
static const std::string ParamSuffix;
};
// foo.cpp:
std::string const Foo::ParamSuffix(FOO_PARAM_SUFFIX);
// bar.h:
#define BAR_PARAM_SUFFIX "prefix"
struct Bar {
static std::string const ParamPrefix;
};
// bar.cpp:
std::string const Bar::ParamPrefix(BAR_PARAM_SUFFIX);
// baz.h:
#include "foo.h"
#include "bar.h"
#define BAZ_PARAM_NAME BAR_PARAM_PREFIX FOO_PARAM_SUFFIX
struct Baz {
static std::string const ParamName;
};
// baz.cpp:
#include "foo.h"
#include "bar.h"
std::string const Baz::ParamName(BAR_PARAM_PREFIX FOO_PARAM_SUFFIX);
I would guess that BAZ_PARAM_NAME
isn't used elsewhere in which case it also doesn't have to be defined: it is defined just to show it can be done. The initialization of Bar::ParamName
assumes that BAZ_PARAM_NAME
isn't defined.
Upvotes: 2