Skip to content

Commit 82ec50b

Browse files
committed
ConfigurableParam: Support for std::string parameter values
This commit enables support for std::string members in parameter classes: ``` struct Param : ConfigurableParamHelper<Param> { std::string name = "defaultString"; }; ``` will now work and be modifyable through the offered APIs. std::string is the only way to represent strings. char* will not work.
1 parent 9add365 commit 82ec50b

File tree

5 files changed

+171
-30
lines changed

5 files changed

+171
-30
lines changed

Common/SimConfig/doc/ConfigurableParam.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ Thereafter, the parameter `ParamA` is automatically registered in a parameter re
8686
8787
* Parameter classes may only contain simple members! Currently the following types are supported
8888
* simple pods (for example `double x; char y;`)
89-
* simple char strings (`char *`)
89+
* std::string
9090
* fixed size arrays of pods using the ROOT way to serialize:
9191
```c++
9292
static constexpr int N=3; //!

Common/SimConfig/include/SimConfig/ConfigurableParam.h

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,9 @@ class ConfigurableParam
105105
static void printAllKeyValuePairs();
106106

107107
// writes a human readable JSON file of all parameters
108-
static void writeJSON(std::string filename);
108+
static void writeJSON(std::string const& filename);
109109
// writes a human readable INI file of all parameters
110-
static void writeINI(std::string filename);
110+
static void writeINI(std::string const& filename);
111111

112112
// can be used instead of using API on concrete child classes
113113
template <typename T>
@@ -120,7 +120,7 @@ class ConfigurableParam
120120
}
121121

122122
template <typename T>
123-
static void setValue(std::string mainkey, std::string subkey, T x)
123+
static void setValue(std::string const& mainkey, std::string const& subkey, T x)
124124
{
125125
auto key = mainkey + "." + subkey;
126126
if (sPtree->get_optional<std::string>(key).is_initialized()) {
@@ -134,7 +134,7 @@ class ConfigurableParam
134134

135135
// specialized for std::string
136136
// which means that the type will be converted internally
137-
static void setValue(std::string key, std::string valuestring)
137+
static void setValue(std::string const& key, std::string const& valuestring)
138138
{
139139
if (sPtree->get_optional<std::string>(key).is_initialized()) {
140140
sPtree->put(key, valuestring);
@@ -157,7 +157,7 @@ class ConfigurableParam
157157
// (certain) key-values
158158
// propagates changes down to each registered configuration
159159
// might be useful to get stuff from the command line
160-
static void updateFromString(std::string);
160+
static void updateFromString(std::string const&);
161161

162162
protected:
163163
// constructor is doing nothing else but
@@ -166,7 +166,7 @@ class ConfigurableParam
166166

167167
static void initPropertyTree();
168168
static bool updateThroughStorageMap(std::string, std::string, std::type_info const&, void*);
169-
static bool updateThroughStorageMapWithConversion(std::string, std::string);
169+
static bool updateThroughStorageMapWithConversion(std::string const&, std::string const&);
170170

171171
virtual ~ConfigurableParam() = default;
172172

@@ -177,7 +177,7 @@ class ConfigurableParam
177177

178178
// static map keeping, for each configuration key, its memory location and type
179179
// (internal use to easily sync updates, this is ok since parameter classes are singletons)
180-
static std::map<std::string, std::pair<int, void*>>* sKeyToStorageMap;
180+
static std::map<std::string, std::pair<std::type_info const&, void*>>* sKeyToStorageMap;
181181

182182
// keep track of provenance of parameters and values
183183
static std::map<std::string, ConfigurableParam::EParamProvenance>* sValueProvenanceMap;

Common/SimConfig/include/SimConfig/ConfigurableParamHelper.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class _ParamHelper
3333
std::map<std::string, ConfigurableParam::EParamProvenance> const* provmap);
3434

3535
static void fillKeyValuesImpl(std::string mainkey, TClass* cl, void*, boost::property_tree::ptree*,
36-
std::map<std::string, std::pair<int, void*>>*);
36+
std::map<std::string, std::pair<std::type_info const&, void*>>*);
3737

3838
static void printWarning(std::type_info const&);
3939

Common/SimConfig/src/ConfigurableParam.cxx

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,19 @@ namespace conf
3030

3131
std::vector<ConfigurableParam*>* ConfigurableParam::sRegisteredParamClasses = nullptr;
3232
boost::property_tree::ptree* ConfigurableParam::sPtree = nullptr;
33-
std::map<std::string, std::pair<int, void*>>* ConfigurableParam::sKeyToStorageMap = nullptr;
33+
std::map<std::string, std::pair<std::type_info const&, void*>>* ConfigurableParam::sKeyToStorageMap = nullptr;
3434
std::map<std::string, ConfigurableParam::EParamProvenance>* ConfigurableParam::sValueProvenanceMap = nullptr;
3535

3636
bool ConfigurableParam::sIsFullyInitialized = false;
3737
bool ConfigurableParam::sRegisterMode = true;
3838

39-
void ConfigurableParam::writeINI(std::string filename)
39+
void ConfigurableParam::writeINI(std::string const& filename)
4040
{
4141
initPropertyTree(); // update the boost tree before writing
4242
boost::property_tree::write_ini(filename, *sPtree);
4343
}
4444

45-
void ConfigurableParam::writeJSON(std::string filename)
45+
void ConfigurableParam::writeJSON(std::string const& filename)
4646
{
4747
initPropertyTree(); // update the boost tree before writing
4848
boost::property_tree::write_json(filename, *sPtree);
@@ -58,6 +58,9 @@ void ConfigurableParam::initPropertyTree()
5858

5959
void ConfigurableParam::printAllKeyValuePairs()
6060
{
61+
if (!sIsFullyInitialized) {
62+
initialize();
63+
}
6164
std::cout << "####\n";
6265
for (auto p : *sRegisteredParamClasses) {
6366
p->printKeyValues(true);
@@ -69,6 +72,9 @@ void ConfigurableParam::printAllKeyValuePairs()
6972
// ... we need to generalize this ... but ok for demonstration purposes
7073
void ConfigurableParam::toCCDB(std::string filename)
7174
{
75+
if (!sIsFullyInitialized) {
76+
initialize();
77+
}
7278
TFile file(filename.c_str(), "RECREATE");
7379
for (auto p : *sRegisteredParamClasses) {
7480
p->serializeTo(&file);
@@ -97,7 +103,7 @@ ConfigurableParam::ConfigurableParam()
97103
sPtree = new boost::property_tree::ptree;
98104
}
99105
if (sKeyToStorageMap == nullptr) {
100-
sKeyToStorageMap = new std::map<std::string, std::pair<int, void*>>;
106+
sKeyToStorageMap = new std::map<std::string, std::pair<std::type_info const&, void*>>;
101107
}
102108
if (sValueProvenanceMap == nullptr) {
103109
sValueProvenanceMap = new std::map<std::string, ConfigurableParam::EParamProvenance>;
@@ -125,7 +131,7 @@ void ConfigurableParam::printAllRegisteredParamNames()
125131
}
126132
}
127133

128-
void ConfigurableParam::updateFromString(std::string configstring)
134+
void ConfigurableParam::updateFromString(std::string const& configstring)
129135
{
130136
if (!sIsFullyInitialized) {
131137
initialize();
@@ -213,7 +219,7 @@ bool ConfigurableParam::updateThroughStorageMap(std::string mainkey, std::string
213219
int type = TDataType::GetType(tinfo);
214220

215221
// check that type matches
216-
if (iter->second.first != type) {
222+
if (iter->second.first != tinfo) {
217223
LOG(WARN) << "Types do not match; cannot update value";
218224
return false;
219225
}
@@ -339,8 +345,21 @@ bool ConvertAndCopy(std::string const& valuestring, void* targetaddr)
339345
}
340346
return false;
341347
}
348+
// special version for std::string
349+
template <>
350+
bool ConvertAndCopy<std::string>(std::string const& valuestring, void* targetaddr)
351+
{
352+
std::string& target = *((std::string*)targetaddr);
353+
if (target.compare(valuestring) != 0) {
354+
// the targetaddr is a std::string to which we can simply assign
355+
// and all the magic will happen internally
356+
target = valuestring;
357+
return true;
358+
}
359+
return false;
360+
}
342361

343-
bool ConfigurableParam::updateThroughStorageMapWithConversion(std::string key, std::string valuestring)
362+
bool ConfigurableParam::updateThroughStorageMapWithConversion(std::string const& key, std::string const& valuestring)
344363
{
345364
// check if key_exists
346365
auto iter = sKeyToStorageMap->find(key);
@@ -349,10 +368,17 @@ bool ConfigurableParam::updateThroughStorageMapWithConversion(std::string key, s
349368
return false;
350369
}
351370

371+
auto targetaddress = iter->second.second;
372+
373+
// treat some special cases first:
374+
// the type is actually a std::string
375+
if (iter->second.first == typeid(std::string)) {
376+
return ConvertAndCopy<std::string>(valuestring, targetaddress);
377+
}
378+
352379
// the type (aka ROOT::EDataType which the type identification in the map) we need to convert to
353-
int targettype = iter->second.first;
380+
int targettype = TDataType::GetType(iter->second.first);
354381

355-
auto targetaddress = iter->second.second;
356382
switch (targettype) {
357383
case kChar_t: {
358384
return ConvertAndCopy<char>(valuestring, targetaddress);

Common/SimConfig/src/ConfigurableParamHelper.cxx

Lines changed: 127 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@
2424

2525
using namespace o2::conf;
2626

27+
bool isString(TDataMember const& dm)
28+
{
29+
return strcmp(dm.GetTrueTypeName(), "string") == 0;
30+
}
31+
2732
// a generic looper of data members of a TClass; calling a callback
2833
// reused in various functions below
2934
void loopOverMembers(TClass* cl, void* obj,
@@ -33,6 +38,10 @@ void loopOverMembers(TClass* cl, void* obj,
3338
for (int i = 0; i < memberlist->GetEntries(); ++i) {
3439
auto dm = (TDataMember*)memberlist->At(i);
3540

41+
auto isValidComplex = [dm]() {
42+
return isString(*dm);
43+
};
44+
3645
// filter out static members for now
3746
if (dm->Property() & kIsStatic) {
3847
continue;
@@ -41,8 +50,8 @@ void loopOverMembers(TClass* cl, void* obj,
4150
LOG(WARNING) << "Pointer types not supported in ConfigurableParams";
4251
continue;
4352
}
44-
if (!dm->IsBasic()) {
45-
LOG(WARNING) << "Complex types not supported in ConfigurableParams";
53+
if (!dm->IsBasic() && !isValidComplex()) {
54+
LOG(WARNING) << "Generic complex types not supported in ConfigurableParams";
4655
continue;
4756
}
4857
const auto dim = dm->GetArrayDim();
@@ -69,15 +78,34 @@ std::string getName(const TDataMember* dm, int index, int size)
6978
return namestream.str();
7079
}
7180

81+
const char* asString(TDataMember const& dm, char* pointer)
82+
{
83+
// first check if this is a basic data type, in which case
84+
// we let ROOT do the work
85+
if (auto dt = dm.GetDataType()) {
86+
return dt->AsString(pointer);
87+
}
88+
89+
// if data member is a std::string just return
90+
else if (isString(dm)) {
91+
return ((std::string*)pointer)->c_str();
92+
}
93+
// potentially other cases to be added here
94+
95+
LOG(ERROR) << "COULD NOT REPRESENT AS STRING";
96+
return nullptr;
97+
}
98+
7299
void _ParamHelper::printParametersImpl(std::string mainkey, TClass* cl, void* obj,
73100
std::map<std::string, ConfigurableParam::EParamProvenance> const* provmap)
74101
{
75102
auto printMembers = [&mainkey, obj, provmap](const TDataMember* dm, int index, int size) {
76103
// pointer to object
77104
auto dt = dm->GetDataType();
78-
char* pointer = ((char*)obj) + dm->GetOffset() + index * dt->Size();
105+
auto TS = dt ? dt->Size() : 0;
106+
char* pointer = ((char*)obj) + dm->GetOffset() + index * TS;
79107
const auto name = getName(dm, index, size);
80-
std::cout << name << " : " << dt->AsString(pointer);
108+
std::cout << name << " : " << asString(*dm, pointer);
81109
if (provmap != nullptr) {
82110
auto iter = provmap->find(mainkey + "." + name);
83111
if (iter != provmap->end()) {
@@ -89,19 +117,84 @@ void _ParamHelper::printParametersImpl(std::string mainkey, TClass* cl, void* ob
89117
loopOverMembers(cl, obj, printMembers);
90118
}
91119

120+
// a function converting a string representing a type to the type_info
121+
// because unfortunately typeid(double) != typeid("double")
122+
// but we can take the TDataType (if it exists) as a hint in order to
123+
// minimize string comparisons
124+
std::type_info const& nameToTypeInfo(const char* tname, TDataType const* dt)
125+
{
126+
if (dt) {
127+
switch (dt->GetType()) {
128+
case kChar_t: {
129+
return typeid(char);
130+
}
131+
case kUChar_t: {
132+
return typeid(unsigned char);
133+
}
134+
case kShort_t: {
135+
return typeid(short);
136+
}
137+
case kUShort_t: {
138+
return typeid(unsigned short);
139+
}
140+
case kInt_t: {
141+
return typeid(int);
142+
}
143+
case kUInt_t: {
144+
return typeid(unsigned int);
145+
}
146+
case kLong_t: {
147+
return typeid(long);
148+
}
149+
case kULong_t: {
150+
return typeid(unsigned long);
151+
}
152+
case kFloat_t: {
153+
return typeid(float);
154+
}
155+
case kDouble_t: {
156+
return typeid(double);
157+
}
158+
case kDouble32_t: {
159+
return typeid(double);
160+
}
161+
case kBool_t: {
162+
return typeid(bool);
163+
}
164+
case kLong64_t: {
165+
return typeid(long long);
166+
}
167+
case kULong64_t: {
168+
return typeid(unsigned long long);
169+
}
170+
default: {
171+
break;
172+
}
173+
}
174+
}
175+
// if we get here none of the above worked
176+
if (strcmp(tname, "string") == 0 || strcmp(tname, "std::string")) {
177+
return typeid(std::string);
178+
}
179+
LOG(ERROR) << "ENCOUNTERED AN UNSUPPORTED TYPE " << tname << "IN A CONFIGURABLE PARAMETER";
180+
return typeid("ERROR");
181+
}
182+
92183
void _ParamHelper::fillKeyValuesImpl(std::string mainkey, TClass* cl, void* obj, boost::property_tree::ptree* tree,
93-
std::map<std::string, std::pair<int, void*>>* keytostoragemap)
184+
std::map<std::string, std::pair<std::type_info const&, void*>>* keytostoragemap)
94185
{
95186
boost::property_tree::ptree localtree;
96187
auto fillMap = [obj, &mainkey, &localtree, &keytostoragemap](const TDataMember* dm, int index, int size) {
97188
const auto name = getName(dm, index, size);
98189
auto dt = dm->GetDataType();
99-
char* pointer = ((char*)obj) + dm->GetOffset() + index * dt->Size();
100-
localtree.put(name, dt->AsString(pointer));
190+
auto TS = dt ? dt->Size() : 0;
191+
char* pointer = ((char*)obj) + dm->GetOffset() + index * TS;
192+
localtree.put(name, asString(*dm, pointer));
101193

102194
auto key = mainkey + "." + name;
103-
using mapped_t = std::pair<int, void*>;
104-
keytostoragemap->insert(std::pair<std::string, mapped_t>(key, mapped_t(dt->GetType(), pointer)));
195+
using mapped_t = std::pair<std::type_info const&, void*>;
196+
auto& ti = nameToTypeInfo(dm->GetTrueTypeName(), dt);
197+
keytostoragemap->insert(std::pair<std::string, mapped_t>(key, mapped_t(ti, pointer)));
105198
};
106199
loopOverMembers(cl, obj, fillMap);
107200
tree->add_child(mainkey, localtree);
@@ -124,16 +217,38 @@ void _ParamHelper::assignmentImpl(std::string mainkey, TClass* cl, void* to, voi
124217
auto assignifchanged = [to, from, &mainkey, provmap](const TDataMember* dm, int index, int size) {
125218
const auto name = getName(dm, index, size);
126219
auto dt = dm->GetDataType();
127-
char* pointerto = ((char*)to) + dm->GetOffset() + index * dt->Size();
128-
char* pointerfrom = ((char*)from) + dm->GetOffset() + index * dt->Size();
129-
if (!isMemblockDifferent(pointerto, pointerfrom, dt->Size())) {
220+
auto TS = dt ? dt->Size() : 0;
221+
char* pointerto = ((char*)to) + dm->GetOffset() + index * TS;
222+
char* pointerfrom = ((char*)from) + dm->GetOffset() + index * TS;
223+
224+
// lambda to update the provenance
225+
auto updateProv = [&mainkey, name, provmap]() {
130226
auto key = mainkey + "." + name;
131227
auto iter = provmap->find(key);
132228
if (iter != provmap->end()) {
133229
iter->second = ConfigurableParam::EParamProvenance::kCCDB; // TODO: change to "current STATE"??
134230
} else {
135231
LOG(WARN) << "KEY " << key << " NOT FOUND WHILE UPDATING PARAMETER PROVENANCE";
136232
}
233+
};
234+
235+
// TODO: this could dispatch to the same method used in ConfigurableParam::setValue
236+
// but will be slower
237+
238+
// test if a complicated case
239+
if (isString(*dm)) {
240+
std::string& target = *(std::string*)pointerto;
241+
std::string const& origin = *(std::string*)pointerfrom;
242+
if (target.compare(origin) != 0) {
243+
updateProv();
244+
target = origin;
245+
}
246+
return;
247+
}
248+
249+
//
250+
if (!isMemblockDifferent(pointerto, pointerfrom, TS)) {
251+
updateProv();
137252
// actually copy
138253
std::memcpy(pointerto, pointerfrom, dt->Size());
139254
}

0 commit comments

Comments
 (0)