Using std::wstring_view to define const std::wstring keys for a map

85 Views Asked by At

I'm trying to setup a structure using a std::map with std::wstring as a key and attempting to use some syntactic surgar to make key declaration/definition easy via macros and std::wstring_view (for const definition of key). Is there a way to use std::wstring_view value as std::wstring map key?

My map is defined as follows...

class Parameter
{
public:
    typedef std::wstring Key; 

    std::variant<void*, int, bool, float, std::wstring> value_;

    ...
}

typedef std::map<Parameter::Key, Parameter> Parameters;

I can access the map no problem using string literals...

myParameters[L"SomeKey"] = L"SomeValue";

However if using a std::wstring_view to define a const key, gives me an error when trying to use it as a key.

static constexpr std::wstring_view SomeKeyDefined = L"SomeKey";
myParameters[SomeKeyDefined] = L"SomeValue";

Results in...

C2679: binary '[': no operator found which takes a right-hand operand of type 'const std::wstring_view' (or there is no acceptable conversion)

It's my understanding that std::wstring_view is simply a wrapper to allow me to define a const std::wstring. Is it possible to use std::wstring_view value as a map key?

Ultimately I would like to be able to have some syntactic sugar (via macros) to aid in defining some const keys. e.g.

#define PARAMETER_KEY_STRING(x) L # x
#define PARAMETER_KEY(name) static constexpr std::wstring_view name = PARAMETER_KEY_STRING(name);

Which would allow me to define const std::wstring map keys as follows

PARAMETER_KEY(SomeKey)

If this cannot be done, is there a another way to have some syntactic sugar for defining std::wstring const keys for a map?

2

There are 2 best solutions below

0
Marek R On

Problem is std::map::operator[] which accepts only key type - in your case std::wstring and the fact there is no implicit conversion from std::wstring_view to std::wstring.

There are two ways to fix it:

myParameters[std::wstring{SomeKeyDefined}] = L"SomeValue";

Or IMO better:

myParameters.emplace(SomeKeyDefined, L"SomeValue");

Demo: https://godbolt.org/z/aYcGeEq5a

0
Ted Lyngmo On

The constructor in std::wstring converting from std::wstring_view (or rather StringViewLike) is explicit so you either need to construct a std::wstring explicitly (std::wstring(my_string_view)) or you could use a transparent comparator and put your map in a wrapper class where you provide an operator[] that can be used with std::wstring_views without having to create a std::wstring every time you do a lookup in the map.

The comparator needs to have some definition of is_transparent and a set of operator() overloads to do comparison between the different types you'd like to be able to compare. In this case, only one overload is needed because a std::wstring_view can be implicitly constructed from a std::wstring, so the comparator becomes rather simple:

struct comp {
    using is_transparent = void;

    bool operator()(std::wstring_view lhs, std::wstring_view rhs) const {
        return lhs < rhs;
    }
};

You'd then fill the wrapper with the member functions you need. Example:

template<class T>
class wstring_map {
    struct comp { /* defined as above */ };

    std::map<std::wstring, T, comp> m_map;
public:
    using value_type = typename decltype(m_map)::value_type;

    wstring_map(std::initializer_list<value_type> il) : m_map(il) {}
    
    // your custom operator[]
    T& operator[](std::wstring_view sv) {        
        if (auto it = m_map.find(sv); it != m_map.end()) return it->second;
        // the key doesn't exist, insert it:
        return m_map.emplace(std::wstring(sv), T{}).first->second;
    }
};

If the find succeeds, no std::wstring needs to be created and a reference to the existing Value will be returned, otherwise a std::wstring Key must be created and inserted in the map to default construct T.

Usage:

int main() {
    wstring_map<std::wstring> map1{
        {L"keyone", L"valueone"},
        {L"keytwo", L"valuetwo"},
    };

    std::wstring_view key1 = L"keyone";
    std::wcout << map1[key1] << '\n'; // valueone
    
    wstring_map<int> map2{
        {L"keyone", 1},
    };
    std::wstring_view key2 = L"keytwo";
    std::wcout << map2[key2] << '\n'; // 0 because keytwo didn't exist before
}

Demo