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);
}
CodePudding user response:
You can't do SFINAE or specialization on function parameters, only template parameters. That means this can't work using your current approach.
What you could do is change your StyleKey
s from being enum
values to being empty tag structs with different types. Then you could specialize KeyValueType
on each of those types and pass an object of one of those types to StyleMap::set
. It can then use the type of that object to deduce the correct KeyValueType
specialization to dispatch to.
namespace StyleKey {
static constexpr inline struct FontFamily {} fontFamily;
static constexpr inline struct FontSize {} fontSize;
};
template <typename KeyType>
struct KeyValueType {};
struct StyleMap;
template <>
struct KeyValueType<StyleKey::FontFamily> {
using value_type = std::string;
static void set(StyleMap *sm, value_type value);
};
template <>
struct KeyValueType<StyleKey::FontSize> {
using value_type = float;
static void set(StyleMap *sm, value_type value);
};
struct StyleMap {
std::string fontFamily = "";
float fontSize = 14;
template <auto key>
void set(typename KeyValueType<std::remove_const_t<decltype(key)>>::value_type value) {
KeyValueType<std::remove_const_t<decltype(key)>>::set(this, value);
}
template <typename KeyType>
void set2(KeyType key, typename KeyValueType<KeyType>::value_type value) {
KeyValueType<KeyType>::set(this, value);
}
};
void KeyValueType<StyleKey::FontFamily>::set(StyleMap *sm, std::string str) {
sm->fontFamily = str;
}
void KeyValueType<StyleKey::FontSize>::set(StyleMap *sm, float sz) {
sm->fontSize = sz;
}
Note: This approach will also work for the goal mentioned in the comment in your main
function. Example.