Why automatic type inference from the initialization list doesn't work in the constructor?

145 Views Asked by At

Why can't I create an instance of a class initialized with an initialization list in the constructor of another class without explicitly specifying the type?

#include <map>
#include <optional>
#include <string>

int main()
{
    std::map<std::string, int> m1({{"test1", 1}, {"test2", 2}, {"test3", 3}});
    std::map<std::string, int> m2{{"test1", 1}, {"test2", 2}, {"test3", 3}};
    auto m3 = std::map<std::string, int>({{"test1", 1}, {"test2", 2}, {"test3", 3}});

    using map_type = std::pair<const std::string, int>;
    std::optional<std::map<std::string, int>>omt1(std::in_place ,{map_type{"test1", 1}, map_type{"test2", 2}, map_type{"test3", 3}});
    std::optional<std::map<std::string, int>>omt2{std::in_place ,{map_type{"test1", 1}, map_type{"test2", 2}, map_type{"test3", 3}}};
    auto omt3 = std::optional<std::map<std::string, int>>(std::in_place ,{map_type{"test1", 1}, map_type{"test2", 2}, map_type{"test3", 3}});


    std::optional<std::map<std::string, int>>om1(std::in_place ,{{"test1", 1}, {"test2", 2}, {"test3", 3}});
    std::optional<std::map<std::string, int>>om2{std::in_place ,{{"test1", 1}, {"test2", 2}, {"test3", 3}}};
    auto om3 = std::optional<std::map<std::string, int>>(std::in_place ,{{"test1", 1}, {"test2", 2}, {"test3", 3}});

    return 0;
}

compiller error:

main.cpp:18:107: error: no matching function for call to ‘std::optional, int> >::optional(const std::in_place_t&, )’
   18 |     std::optional<std::map<std::string, int>>om1(std::in_place ,{{"test1", 1}, {"test2", 2}, {"test3", 3}});
      |                                                                                                           ^
2

There are 2 best solutions below

1
kofni On

The Problem: In the line that's causing the error:

std::optional<std::map<std::string, int>>om1(std::in_place ,{{"test1", 1}, {"test2", 2}, {"test3", 3}});

You're using direct initialization, and the compiler is having trouble deducing the type of {{"test1", 1}, {"test2", 2}, {"test3", 3}}. It's ambiguous whether this is an initializer list of pairs or something else.

The Solution:

Use list initialization (i.e., curly braces) for the std::optional

std::optional<std::map<std::string, int>> om1{std::in_place, {{"test1", 1}, {"test2", 2}, {"test3", 3}}};
6
Daniel Langr On

You can make use of the (C++17) deduction guides for std::optional as follows:

std::optional o(
  std::map<std::string, int>{{"test1", 1}, {"test2", 2}, {"test3", 3}}
);

Live demo: https://godbolt.org/z/rfK3WxYqT

The type of the map is written only once, and there is no need for your map_type.


UPDATE

As already mentioned, the problem with your definition:

std::optional<std::map<std::string, int>> om1(
  std::in_place ,{{"test1", 1}, {"test2", 2}, {"test3", 3}}
);

is that a compiler is not able to deduce the template argument for std::initializer_list. A simple demo of this problem:

template <typename T>
void f(std::initializer_list<T>) { }

int main()
{
  f({1, 2, 3});  // OK; T deduced as int
  f({{1, 1}, {2, 2}, {3, 3}}); // ERROR
}

Live demo: https://godbolt.org/z/KofYjdz6v