I wanted to create a string-container type that checks if the string it contains is valid at construction. In my opinion, it should be preferable if this class has an constructor that takes a std::string as an argument (just to make sure that no implicit type conversions happen).
But when I use this string-container in an std::initializer_list (and even further in a std::unordered_multimap) I get an error, if I define the constructor as explicit.
Is there a way to define the constructor as explicit and use it at the same time in a std::multimap? (i.e. a constructor-overload of a magic type I am currently not aware of)
I prepared the following minimal example that should show the point:
#include <utility>
#include <string>
#include <stdexcept>
#include <unordered_map>
class constrained_string_container_t{
public:
// variant 1: works
//constrained_string_container_t(const std::string& inst) : str{inst} {validate();}
// variant 2: doesn't work
explicit constrained_string_container_t(const std::string& inst) : str{inst} {validate();}
explicit constrained_string_container_t(std::string&& inst) : str{std::move(inst)} {validate();}
// variant 3: works
//template<typename STR_T>
//constrained_string_container_t(STR_T&& inst) : str{std::forward<STR_T>(inst)} {validate();}
// variant 4: doesn't work
//template<typename STR_T>
//explicit constrained_string_container_t(STR_T&& inst) : str{std::forward<STR_T>(inst)} {validate();}
void validate() {if (str == "abc") {throw std::runtime_error{"abc not allowed"};}}
private:
std::string str;
};
using pair_t = std::pair<std::string, constrained_string_container_t>;
using init_pair_list_t = std::initializer_list<std::pair<std::string, constrained_string_container_t>>;
using map_t = std::unordered_multimap<std::string, constrained_string_container_t>;
int main() {
using namespace std::string_literals;
//suceeds for all variants
pair_t p0{std::string{"test"}, std::string{"test2"}};
//fails to compile for variant 2 and variant 4
init_pair_list_t p1{{std::string{"test"}, std::string{"test2"}}};
//fails to compile for variant 2 and variant 4
init_pair_list_t p2{{"test"s, "test2"s}};
//fails to compile for variant 2 and variant 4
map_t m{{"test"s, "test2"s}};
}
When I compile it, I get the following errors (with gcc-10):
explicit_shit.cpp: In function ‘int main()’:
explicit_shit.cpp:40:72: error: could not convert ‘{{std::__cxx11::basic_string<char>(((const char*)"test"), std::allocator<char>()), std::__cxx11::basic_string<char>(((const char*)"test2"), std::allocator<char>())}}’ from ‘<brace-enclosed initializer list>’ to ‘init_pair_list_t’ {aka ‘std::initializer_list<std::pair<std::__cxx11::basic_string<char>, constrained_string_container_t> >’}
40 | init_pair_list_t p1{{std::string{"test"}, std::string{"test2"}}};
| ^
| |
| <brace-enclosed initializer list>
explicit_shit.cpp:43:48: error: could not convert ‘{{std::literals::string_literals::operator""s(const char*, std::size_t)(4), std::literals::string_literals::operator""s(const char*, std::size_t)(5)}}’ from ‘<brace-enclosed initializer list>’ to ‘init_pair_list_t’ {aka ‘std::initializer_list<std::pair<std::__cxx11::basic_string<char>, constrained_string_container_t> >’}
43 | init_pair_list_t p2{{"test"s, "test2"s}};
| ^
| |
| <brace-enclosed initializer list>
explicit_shit.cpp:46:36: error: no matching function for call to ‘std::unordered_multimap<std::__cxx11::basic_string<char>, constrained_string_container_t>::unordered_multimap(<brace-enclosed initializer list>)’
46 | map_t m{{"test"s, "test2"s}};
| ^
init_pair_list_t p1{{std::string{"test"}, constrained_string_container_t{std::string{"test2"}}}};