5

Given the following code, I was surprised that try_emplace could not work out to use the default constructor demonstrated in the first line of the main function, instead complaining there is no matching function call to Element::Element(double, double). Have I misunderstood the way the compiler creates default constructors or the usage of try_emplace? I can of course get this code to work by defining an all parameter ctors for Element, but this seems redundant.

#include <string>
#include <map>

struct Element
{    
    double a;
    double b;
};

int main(int argc, char** argv)
{
    Element e {2.0, 3.0};

    std::map<std::string, Element> my_map;
    my_map.try_emplace("hello", 2.0, 3.0);

    return 0;
}
4
  • 2
    The compiler doesn't generate c'tor s with parameters, and try_emplace will only try to call a c'tor (no aggregate initialisation).
    – George
    Commented Aug 21, 2018 at 10:49
  • But then where does the definition for Element e{2.0, 3.0} come from?
    – Madden
    Commented Aug 21, 2018 at 10:51
  • 1
    @Madden Element is an aggregate (a POD actually), for which brace initialization is supported, even without any constructor. I think that this is a deficiency in the standard, and it should have had a special code in emplace_back for aggregates. This would have been more consistent and less surprising. Commented Aug 21, 2018 at 11:06
  • For unknown reason the STL has poor support for aggregates!
    – Oliv
    Commented Aug 21, 2018 at 11:21

2 Answers 2

5

Alternatively, you can opt to not define any user-defined ctors in Element but rather use those that are still defined no matter what (unless explicitly deleted):

    my_map.try_emplace("hello", Element{2.0, 3.0});
4
  • Will this method entail a copy of the Element object?
    – Madden
    Commented Aug 21, 2018 at 11:02
  • @Madden - Yes. But seeing as this type is a simple trivially copiable aggregate, it likely won't do any worse than forwarding two doubles. Don't optimize prematurely. Commented Aug 21, 2018 at 11:05
  • Sure, but this was more of a simple example - in practice I intend to do a lot of emplacing and with much more complex/larger objects. I will define specific constructors to make sure I avoid the copy
    – Madden
    Commented Aug 21, 2018 at 11:10
  • @Madden Such templated functions usually forward their arguments, so if you have proper overloads accepting rvalue references, they should be called.
    – bipll
    Commented Aug 21, 2018 at 11:13
4

This is due to the fact that emplace methods attempt to instantiate elements with parentheses, not by aggregate initialization. You can verify that

Element e(2.0, 3.0);

fails to compile, because there is no such constructor (Element e{2.0, 3.0} bypasses such a constructor call). But this is what emplace tries to do. To fix this, you can add an appropriate constructor:

struct Element
{
    Element(double a, double b) : a(a), b(b) {}

    double a;
    double b;
};

Not the answer you're looking for? Browse other questions tagged or ask your own question.