https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96088
--- Comment #9 from Jonathan Wakely <redi at gcc dot gnu.org> --- There are so many ways to break our current optimizations that try to avoid creating temporaries: #include <unordered_set> #include <unordered_map> #include <iterator> struct K { explicit K(int) noexcept { } bool operator==(const K&) const { return true; } }; template<> struct std::hash<K> { auto operator()(const K&) const { return 0ul; } }; int i[1]{}; std::unordered_set<K> s(i, i); This fails because it tries to do the lookup using an int, which is not a valid argument to std::hash<K>. The int can't be implicitly converted to K because the constructor is explicit. For Antony's original example in comment 0 the implicit conversion from const char* to std::string is valid, so the lookup causes an allocation. So the optimization either makes valid code fail to compile, or causes unwanted extra conversions. Another way to make it fail would be: struct Hash { auto operator()(const K&) const { return 0ul; } auto operator()(int) const { return 1ul; } }; struct Eq { bool operator()(const K&, const K&) const { return true; } bool operator()(const K&, int) const { return false; } bool operator()(int, const K&) const { return false; } }; int i[2]{}; std::unordered_set<K, Hash, Eq> s(i, i+2); Here we can pass an int to the hash function. It compiles OK, but gives the wrong hash code, so we insert two identical values into a unique container. We should just stop trying to avoid creating a temporary.