8

I have a map of int -> { basic types } I need to store.

I would like to simple create a struct { int f1, int f2; }; and store the values directly, constructing the struct in situ during the store. I do not expect there to be any repeated keys, so try_emplace seems ideal.

I wrote this code:

// mcve.cpp
#include <map>
#include <string>

struct various { int f1, f2; };
using map_t = std::map<int, various>;

void
example()
{
    map_t dict;

    //dict.try_emplace(1, 2);
    dict.try_emplace(1, 1, 2);
    //dict.try_emplace(1, {1, 2});
}

But none of these options work.

Using clang++ I get errors like this. (Version: clang version 5.0.1 (tags/RELEASE_501/final))

/opt/local/libexec/llvm-5.0/include/c++/v1/tuple:1365:7: error: no matching
      constructor for initialization of 'various'
      second(_VSTD::forward<_Args2>(_VSTD::get<_I2>(__second_args))...)
      ^      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

### ... many lines ...

mcve.cpp:14:7: note: in instantiation of function template specialization
      'std::__1::map<int, various, std::__1::less<int>,
      std::__1::allocator<std::__1::pair<const int, various> > >::try_emplace<int,
      int>' requested here
        dict.try_emplace(1, 1, 2);
             ^
mcve.cpp:4:8: note: candidate constructor (the implicit copy constructor) not
      viable: requires 1 argument, but 2 were provided
struct various { int f1, f2; };
       ^
mcve.cpp:4:8: note: candidate constructor (the implicit move constructor) not
      viable: requires 1 argument, but 2 were provided
mcve.cpp:4:8: note: candidate constructor (the implicit default constructor) not
      viable: requires 0 arguments, but 2 were provided
1 error generated.

Using g++ I get errors like this. (Version: g++ (MacPorts gcc7 7.2.0_0) 7.2.0):

In file included from /opt/local/include/gcc7/c++/bits/node_handle.h:40:0,
                 from /opt/local/include/gcc7/c++/bits/stl_tree.h:72,
                 from /opt/local/include/gcc7/c++/map:60,
                 from mcve.cpp:1:
/opt/local/include/gcc7/c++/tuple: In instantiation of 'std::pair<_T1, _T2>::pair(std::tuple<_Args1 ...>&, std::tuple<_Args2 ...>&, std::_Index_tuple<_Indexes1 ...>, std::_Index_tuple<_Indexes2 ...>) [with _Args1 = {int&&}; long unsigned int ..._Indexes1 = {0}; _Args2 = {int&&, int&&}; long unsigned int ..._Indexes2 = {0, 1}; _T1 = const int; _T2 = various]':

### ... many lines ...

mcve.cpp:14:26:   required from here
/opt/local/include/gcc7/c++/tuple:1652:70: error: no matching function for call to 'various::various(int, int)'
         second(std::forward<_Args2>(std::get<_Indexes2>(__tuple2))...)
                                                                      ^
mcve.cpp:4:8: note: candidate: various::various()
 struct various { int f1, f2; };
        ^~~~~~~
mcve.cpp:4:8: note:   candidate expects 0 arguments, 2 provided
mcve.cpp:4:8: note: candidate: constexpr various::various(const various&)
mcve.cpp:4:8: note:   candidate expects 1 argument, 2 provided
mcve.cpp:4:8: note: candidate: constexpr various::various(various&&)
mcve.cpp:4:8: note:   candidate expects 1 argument, 2 provided

So, how can I efficiently store data into my map (ideally, using simple, readable code)?

aghast
  • 14,785
  • 3
  • 24
  • 56
  • 2
    The simplest way is to just add a constructor to `various`. –  Jan 22 '18 at 22:36
  • @Frank That doesn't seem simple. I don't even want this thing to have a name. – aghast Jan 22 '18 at 23:18
  • In that case, might I suggest you provide us with an example demonstrating what you are attempting to do. Otherwise, you'll get what I and @Praetorian are giving you: ways of making your code as posted work. –  Jan 22 '18 at 23:48

1 Answers1

13

Your first two attempts

dict.try_emplace(1, 2);
dict.try_emplace(1, 1, 2);

fail because you cannot use try_emplace, emplace etc. to initialize an aggregate from a list of values. Standard allocators construct the types in-place using () instead of {}, which of course will fail for aggregates. See the answers to this question.

The third attempt

dict.try_emplace(1, {1, 2});

fails because a braced-init-list is not an expression, and doesn't have a type, so template argument deduction fails to deduce it as various.

You can get try_emplace to work by specifying you're constructing a various instance

dict.try_emplace(1, various{1, 2});

Or add appropriate constructor(s) to various, which will allow the first two attempts to work.

However, given that you're working with a map containing builtin types, the easiest solution is to just use insert instead of emplacement.

dict.insert({1, {1, 2}});

The map::insert overload called above is std::pair<iterator,bool> insert(value_type&&), where value_type is pair<const int, various>. The nested braced-init-lists are deduced as value_type by the overload resolution rules in over.match.list, specifically the second bullet

If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.

The pair(T1 const&, T2 const&) constructor is selected.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • Can you elaborate on why the `insert` works? It looks like the outer braces are a pair, yes? – aghast Jan 22 '18 at 23:14
  • 4
    @AustinHastings `insert` works because no type deduction is involved in this case, its parameter type (`value_type`) is known. The outer braces are indeed deduced as a `pair`, edited the answer to explain that in detail. – Praetorian Jan 22 '18 at 23:49
  • @Praetorian are you aware of any plans to address this and to allow `try_emplace(1, {1, 2});` in C++20? – Dev Null Aug 24 '18 at 11:41
  • @DevNull None that I've seen. It would require template argument deduction to deduce a type from `{...}` and given that the special allowance for `auto` to do so was controversial, it might be unlikely to happen. – Praetorian Aug 25 '18 at 21:56
  • Looks like `try_emplace` works? https://stackoverflow.com/a/39881701/558892 – Deqing Feb 24 '20 at 00:20
  • @Deqing that's because in that example they have the appropriate constructor – Thomas Mouton Feb 24 '22 at 22:08