Search code examples
c++c++11setstd

Why does an std::insert on an std::set of constant-sized arrays not work as expected?


I am in a scenario where I need to process a variable-sized array of constant-sized C-strings, namely: I want to know if each of those strings is unique.
I tried using a std::set, as uniqueness of its contents is a given (note: I used a single const-sized array here, as errors produced are the same):

#include <set>

int main(){
    std::set<wchar_t[4]> sSet;
    const wchar_t[4] text = L":)\0";
    // checking if successful
    // set.insert() returns a pair of <iterator, bool>
    // when used like this
    std::pair<std::set<wchar_t[4]>::iterator, bool> result = \
    sSet.insert(text);
    if(!result.second) std::cout << "Value not unique!";
    return 0;
}

The mentioned code crashes in a spectacular manner:

/usr/include/c++/11/bits/alloc_traits.h:518:28: error: no matching function for call to ‘construct_at(wchar_t (*&)[4], const wchar_t [4])’
  518 |           std::construct_at(__p, std::forward<_Args>(__args)...);
      |           ~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(note: I skipped a lot of stack tracing here)
/usr/include/c++/11/bits/stl_construct.h:96:56: error: invalid conversion from ‘const wchar_t*’ to ‘wchar_t’ [-fpermissive]
   96 |     -> decltype(::new((void*)0) _Tp(std::declval<_Args>()...))
      |                                     ~~~~~~~~~~~~~~~~~~~^~
      |                                                        |
      |                                                        const wchar_t*

(a dozen or so notes follow,
some of them are about passing a wrong number of parameters, 
and most are beyond me, see next line)
/usr/include/c++/11/bits/stl_set.h:518:7: note: candidate: ‘std::pair, _Compare, typename __gnu_cxx::__alloc_traits<_Alloc>::rebind<_Key>::other>::const_iterator, bool> std::set<_Key, _Compare, _Alloc>::insert(std::set<_Key, _Compare, _Alloc>::value_type&&) [with _Key = wchar_t [4]; _Compare = std::less; _Alloc = std::allocator; typename std::_Rb_tree<_Key, _Key, std::_Identity<_Tp>, _Compare, typename __gnu_cxx::__alloc_traits<_Alloc>::rebind<_Key>::other>::const_iterator = std::_Rb_tree, std::less, std::allocator >::const_iterator; typename __gnu_cxx::__alloc_traits<_Alloc>::rebind<_Key>::other = std::allocator; typename __gnu_cxx::__alloc_traits<_Alloc>::rebind<_Key> = __gnu_cxx::__alloc_traits, wchar_t [4]>::rebind; typename _Alloc::value_type = wchar_t [4]; std::set<_Key, _Compare, _Alloc>::value_type = wchar_t [4]]’
  518 |       insert(value_type&& __x)
      |       ^~~~~~

Compiling with MSVC yielded something entirely different:

error C3074: an array can only be initialized with an initializer-list

(curly bracing, as per error's suggestion, did not work)

As it seems to be an error inside of stdlibs' files, I am curious: what happened here? Approach I've shown works perfectly with sets of types which are not arrays, like std::set<int>.
I suspect that I violated the intended syntax, which is insert(value_type&& value);, as per a reference (note: my target standard is C++11). If so, what have I done incorrectly? I find the error messages inconclusive, and toying around with std's files is yet beyond me.

I know my initial problem can be solved in various ways, I'm using a vector of vectors for now - I'm still intrigued by this std::set behaviour, though.


Solution

  • Guess this has been more or less answered in the comments, but I think it might be worth pulling it all together to help you on your way.

    When declaring a set (and other STL containers), the type of the elements in a set needs to be a 'value type', and a C-style array ain't. But std::array is, so, as Ted says, use that instead.

    Knowing that, the changes you need to make should be obvious, but, being the pedant that I am, I've made them anyway:

    #include <iostream>
    #include <set>
    #include <utility>
    #include <array>
    
    using wc4 = std::array <wchar_t, 4>;
    
    int main ()
    {
        std::set <wc4> sSet;
        wc4 text = { L":)\0" };
        auto result = sSet.insert (text);
        if (!result.second)
            std::cout << "Value not unique!";
    }
    

    Notes:

    1. Your code didn't crash. It failed to compile. But yes, C++ compiler error messages can be messy, at best.
    2. Use auto, when you can.
    3. Not sure why you slipped that \0 in there. Now you have two.
    4. Please make sure you, erm, include all the relevant #includes. It will stand you in good stead later on in your career, if nothing else. utility was missing here (for std::pair).
    5. I know my initial problem can be solved in various ways, I'm using a vector of vectors for now well, we can't see that code, so, 🤷