map::emplace with multiple parameter constructor ?

The Partridge Family were neither partridges nor a family. Discuss.
binbinhfr
Posts: 78
Joined: May 9th, 2019, 10:57 pm

map::emplace with multiple parameter constructor ?

Post by binbinhfr » May 17th, 2020, 12:46 pm

Hi,

trying to use map::emplace() with a multiple parameters constructor....
like this :

Code: Select all

class A
{
  A(int _a, int _b) : a(_a), b(_b) {};
  float a, b;
}

std::multimap<int,A> map;

map.emplace(12, 2.5,3.5);
does not work... he tries to find a constructor with 3 members, as if he was ignoring the first parameter is the key...

I just want to avoid using a temporary object... and something like

Code: Select all

map.emplace(12, A(2.5,3.5));

User avatar
AleksiyCODE
Posts: 30
Joined: September 21st, 2019, 8:47 pm

Re: map::emplace with multiple parameter constructor ?

Post by AleksiyCODE » May 17th, 2020, 4:06 pm

This works:

Code: Select all

#include <map>
class A
{
public:
    A(int _a, int _b) : a(_a), b(_b) {};
    float a, b;
};

int main()
{
    std::multimap<int, A> map;
    map.emplace(12, A{ 2, 3 });
    return 0;
}
When you call emplace with 3 parameters compiler does not know what to do with them
'std::pair<const int,A>::pair': no overloaded function takes 3 arguments
because emplace creates a pair, and to create a pair you need 2 parameters.

now

Code: Select all

    map.emplace(12, { 2, 3 });
does not work
no instance of function template "std::multimap<_Kty, _Ty, _Pr, _Alloc>::emplace [with _Kty=int, _Ty=A, _Pr=std::less<int>, _Alloc=std::allocator<std::pair<const int, A>>]" matches the argument list
because (i guess) the compiler is not certain what object to construct with {}

If you tell explicitly what kind of pair you want to create - it works

Code: Select all

    map.emplace(std::make_pair<int,A>(12, { 2, 3 }));
but now there is little point in using emplace, might as well just insert

Code: Select all

    map.insert  (std::make_pair<int, A>(12, { 2, 3 }));

another way to tell compiler what to create with {} is like this

Code: Select all

    map.emplace( 12, A{ 2, 3 } );
and this is the best way i know
I like ass

albinopapa
Posts: 4145
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: map::emplace with multiple parameter constructor ?

Post by albinopapa » May 17th, 2020, 5:36 pm

What about:

Code: Select all

map.emplace(
    std::piecewise_construct,
    std::forward_as_tuple( 12 ), 
    std::forward_as_tuple( 2, 3 )
);
From multimap/emplace - cppreference.com
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4145
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: map::emplace with multiple parameter constructor ?

Post by albinopapa » May 17th, 2020, 5:41 pm

It is unfortunate though that the library isn't written in a way to take the first param as key, however, what if key_type also had multiple parameter constructor?

As you stated, the type needed for the emplace function to work is an std::pair<key_type, value_type>, so the best you can hope for is forwarding the values to construct a pair in place, but it's always going to be copied/moved into the map.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

binbinhfr
Posts: 78
Joined: May 9th, 2019, 10:57 pm

Re: map::emplace with multiple parameter constructor ?

Post by binbinhfr » May 17th, 2020, 8:16 pm

hi guys,

as I said in my original post, I tried the map.emplace(12, A(2.5,3.5)) option, but I wish there was an option to avoid it, because it calls a temporary constructor/copy. Emplace is supposed to avoid that as specified in the doc...
But I'll do this.
PS: I also saw the forward_as_tuple version, but, honestly, it's horrible. ;-)

albinopapa
Posts: 4145
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: map::emplace with multiple parameter constructor ?

Post by albinopapa » May 18th, 2020, 12:47 am

Emplace is supposed to avoid that as specified in the doc...
Yeah, the problem is std::pair itself only accepts in-place construction as:

Code: Select all

	template<class... _Types1, class... _Types2> inline
		pair( piecewise_construct_t, tuple<_Types1...> _Val1, tuple<_Types2...> _Val2);
So basically, the mutlimap::emplace function is doing exactly as intended. It constructs the std::pair in-place, it doesn't guarantee that the objects of the std::pair are constructed in-place, especially since it already has a way of doing it, using the std::piecewise_t version of the pair::pair constructor.

So whether you like it or not, to get what you want, use the piecewise_t version with std::forward_as_tuple or deal with the construction of a temp A being possibly copy/moved or elided and the pair being constructed in-place in the multimap.

I was hoping to rely on C++17 guaranteed copy/move elision, but in this case, I don't think this applies since std::pair has a move constructor it will be used for temporaries ( rvalues ). Which means a temporary will be created and the temporary will be copied into the std::pair. However, the std::pair object itself should be constructed in place where the int ( key_type ) and the temporary A ( value_type ) will be copied into the memory using the in-place new operator.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

User avatar
AleksiyCODE
Posts: 30
Joined: September 21st, 2019, 8:47 pm

Re: map::emplace with multiple parameter constructor ?

Post by AleksiyCODE » May 18th, 2020, 1:43 pm

.Yea, it's time for me to learn to read questions before answering.
I payed very little attention to piecewise construct and have never used it. It seems, the time has come to make a change.
I like ass

albinopapa
Posts: 4145
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: map::emplace with multiple parameter constructor ?

Post by albinopapa » May 18th, 2020, 9:19 pm

I suppose what would have been nice of the standards committee to have written this is something like:

Code: Select all

template<typename...Args>
iterator emplace( ktype&& key_, Args&&... args ){
    auto* node = tree.get_next_node_location( key_ );
    node = new (node) val_type{ std::forward<Args>( args )... );
}
I really don't know how the tree is implemented internally, but something like that would have worked for simple key types like int, but if the key type also had a multi-parameter constructor, there wouldn't be a way to distinguish between the two constructors. So, the emplace function just assumes that you are passing two values that will be forwarded to std::pair's constructor, in your example case: pair<int, A>, so the pair itself is what is placed in-place and the values { 1, A{ 2, 3 } } are passed to std::pair::pair<int, A>( int&&, A&& ). The int will be copied since it's a fundamental type, and the A will be "moved" if it has a move constructor whether it be implicitly or explicitly defined. In your example though, A having only basic types, it will of course be copied.

As an aside, I've often wondered about doing something like this:

Code: Select all

struct A_Data{
    int var_a = 0;
    int var_b = 0;
    std::string name;
};

class A{
public:
    A()
        :
    pdata( new A_Data )
    {}
    A( A const& other )
        :
    pdata( new A_Data{ *other.pdata } ) 
    {}
    A( A&& other )noexcept
        :
    pdata( other.pdata )
    {
        other.pdata = nullptr;
    }
    ~A()noexcept{
        delete pdata;
    }

    A( int va, int vb, std::string name )
        :
    pdata( new A_Data{ va, vb, std::move(name) )
    {}
    A& operator=( A const& other ){
        *this = A{ other };
        return *this;
    }
    A& operator=( A&& other )noexcept{
        if( this != &other ){
            delete pdata;
            pdata = other.pdata;
            other.pdata = nullptr;
        }
        return *this;
    }

    int get_var_a()const noexcept{ return pdata->var_a; }
    int get_var_b()const noexcept{ return pdata->var_b; }
    std::string const& get_name()const noexcept{ return pdata->name; }

    void set_var_a( int value_ )noexcept{ pdata->var_a = value_; }
    void set_var_a( int value_ )noexcept{ pdata->var_b = value; }
    void set_name( std::string value_ )noexcept{ pdata->name = std::move( value_ ); }
private:
    A_Data* pdata = nullptr;
};
The reasoning behind doing something like this would be to get move semantics out of your objects. You'd be able to instantiate an A object, treat it like a value object and get the speed up from move semantics working with it instead of copy semantics.

The drawbacks are:
  • The dynamic allocation and deallocations would probably negate the benefits of being able to quickly move these types of objects.
  • Having to define copy and move constructors in every class you made like this.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

Slidy
Posts: 67
Joined: September 9th, 2017, 1:19 pm

Re: map::emplace with multiple parameter constructor ?

Post by Slidy » May 20th, 2020, 5:33 pm

Doesn't the emplace function perform a move? Don't think there are any copies performed here. If you think a move is still expensive for your object you could always store a pointer (preferably unique_ptr) rather than the object itself, then construct the object using new.

binbinhfr
Posts: 78
Joined: May 9th, 2019, 10:57 pm

Re: map::emplace with multiple parameter constructor ?

Post by binbinhfr » May 20th, 2020, 7:52 pm

@albi: you like to play with the theory, uh ? are you a professional programmer, or maybe working on the computer research side ?

@slidy : you make me doubt. I was sure that the addition of this constructor was doubling the emplace implicit constructor. They say :
"This new element is constructed in place using args as the arguments for the construction of a value_type (which is an object of a pair type)."
So Emplace seems to already call a constructor. I have to check by using a verbose constr.

Post Reply