I'm experimenting with C++ templates, and a kind of heterogenous type-safe map. Keys go with specific types. An example use would be something like a CSS stylesheet. I've got it to where I can things write:
styles.set<StyleKey::fontFamily>("Helvetica");
styles.set<StyleKey::fontSize>(23.0);
That type checks as desired; it won't compile calls where the key does not match it's intended value type. But I'm wondering if there's also a way to write it like this:
styles.set(StyleKey::fontFamily, "Helvetica");
styles.set(StyleKey::fontSize, 23.0);
... and have it deduce the same thing, because the first argument is a constant.
Here's my flailing attempt, pasted below and on godbolt. The set2
template does not work.
#include <iostream>
#include <string>
using namespace std;
struct color {
float r,g,b;
};
ostream &operator <<(ostream &out, const color &c) {
return out << "[" << c.r << ',' << c.g << ',' << c.b << "]";
}
// Gives something that would have types: string, float, color, bool
enum class StyleKey {
fontFamily = 1, fontSize, fontColor, visible
};
template <StyleKey key>
struct KeyValueType {
};
struct StyleMap;
template <>
struct KeyValueType<StyleKey::fontFamily> {
typedef string value_type;
static void set(StyleMap *sm, value_type value);
};
template <>
struct KeyValueType<StyleKey::fontSize> {
typedef float value_type;
static void set(StyleMap *sm, value_type value);
};
struct StyleMap {
string fontFamily = "";
float fontSize = 14;
color fontColor = color{0,0,0};
bool visible = true;
template <StyleKey key>
void set(typename KeyValueType<key>::value_type value) {
cout << "set " << (int)key << " value: " << value << endl;
KeyValueType<key>::set(this, value);
}
template <StyleKey key>
void set2(StyleKey key2, typename KeyValueType<key>::value_type value) {
static_assert(key == key2);
cout << "set " << (int)key << " value: " << value << endl;
}
};
void KeyValueType<StyleKey::fontFamily>::set(StyleMap *sm, string str) {
sm->fontFamily = str;
}
void KeyValueType<StyleKey::fontSize>::set(StyleMap *sm, float sz) {
sm->fontSize = sz;
}
void print(const StyleMap &sm) {
cout << "font family : " << sm.fontFamily << endl;
cout << "font size : " << sm.fontSize << endl;
cout << "color : " << sm.fontColor << endl;
cout << "visible : " << sm.visible << endl;
}
int main() {
// Goal:
//
// StyleMap styles;
// styles[fontFamily] = "Helvetica";
// styles[fontSize] = 15.0;
// string fam = styles[fontFamily]
// float sz = styles[fontSize];
StyleMap styles;
// This works!
styles.set<StyleKey::fontFamily>("Helvetica");
styles.set<StyleKey::fontSize>(23.0);
// This won't compile, as desired
// styles.set<StyleKey::fontFamily>(20);
// But can we write it like this?
// styles.set2(StyleKey::fontFamily, "Helvetica");
// styles.set2(StyleKey::fontSize, 23.0);
print(styles);
}