Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::map of tuple to tuple and using emplace

Tags:

c++

c++17

Consider the following code, compiled with g++ 7.0.1 (-std=c++17):

#include <map>
#include <tuple>

int main()
{
    // Create an alias for a tuple of three ints
    using ThreeTuple=std::tuple<int,int,int>;
    // Create an alias for a map of tuple to tuple (of three ints)
    using MapThreeTupleToThreeTuple=std::map<ThreeTuple,ThreeTuple>;

    MapThreeTupleToThreeTuple m;

    // The following does NOT compile
    m.emplace({1,2,3},{4,5,6});

    // ..., and neither does this
    m.emplace(std::piecewise_construct,{1,2,3},{4,5,6});
}

I would have thought that the initializer_list arguments to map::emplace() would have sufficed and would have resulted in the insertion of the tuple key to tuple value association as specified. Apparently, the compiler disagrees.

Of course creating a tuple explicitly (i.e., ThreeTuple{1,2,3} instead of just {1,2,3}) and passing that to map::emplace() solves the problem, but why can't the initializer lists be passed directly to map::emplace() which would automatically forward them to the tuple constructors?

like image 684
Michael Goldshteyn Avatar asked May 24 '17 15:05

Michael Goldshteyn


2 Answers

AFAIK, no changes in C++17 matter in this context. As explained by NathanOliver and Barry, {1,2,3} cannot be deduced to have any type and hence cannot be matched against a template argument. You must provide the arguments for the constructor of ThreeTuple as deducible types, i.e.

m.emplace(std::piecewise_construct,
          std::forward_as_tuple(1,2,3),
          std::forward_as_tuple(4,5,6));

which calls the constructor

template<typename T1, typename T2>
template<typename... Args1, typename... Args2 >
std::pair<T1,T2>::pair(std::piecewise_construct_t,
                       std::tuple<Args1...>, std::tuple<Args2...>);

In this particular case, you can even omit the std::piecewise_construct

m.emplace(std::forward_as_tuple(1,2,3),
          std::forward_as_tuple(4,5,6));

or (in C++17 as pointed out by Nicol in a comment)

m.emplace(std::tuple(1,2,3), std::tuple(4,5,6));

which are equivalent to

m.emplace(ThreeTuple(1,2,3), ThreeTuple(4,5,6));

and call the constructor

template<typename T1, typename T2>
std::pair<T1,T2>::pair(const&T1, const&T2);

Note also that AFAIK you cannot get this working by using std::initializer_list<int> explicitly. The reason is simply that there is not suitable constructor for pair<ThreeTuple,ThreeTuple> (the value_type of your map).

like image 88
Walter Avatar answered Oct 15 '22 00:10

Walter


but why can't the initializer lists be passed directly to map::emplace()

Because initializer lists aren't expressions and so they don't have types. The signature for emplace() is just:

template< class... Args >
std::pair<iterator,bool> emplace( Args&&... args );

and you can't deduce a type from {1,2,3}. You couldn't in C++11 and you still can't in C++1z. The only exception to this rule is if the template parameter is of the form std::initializer_list<T> where T is a template parameter.

In order for m.emplace({1,2,3},{4,5,6}); to work, you'd need a signature like:

std::pair<iterator,bool> emplace(key_type&&, mapped_type&&);
like image 32
Barry Avatar answered Oct 15 '22 00:10

Barry