I'm getting a compiler error about a type conversion that should be legal, to my eyes.
Let's say I have a home-grown string class, convertible from and to char*
:
class custom_string
{
public:
custom_string(const char* str) : _str{str} {}
operator const char*() const { return _str; }
private:
const char* _str;
};
Out in the wild, I want to use this custom class interchangeably with std::string
in a number of ways:
struct pod_t
{
std::string str;
int other_data;
};
void test()
{
custom_string cstr("Hello");
std::set<std::string> strings;
strings.emplace(cstr);
pod_t pod {cstr, 42}; // C2440: 'initializing': cannot convert from 'custom_string' to 'std::string'
}
I'm using MSVC with the /std:c++20
flag.
Why does the last line result in a compiler error? If the compiler can figure out the path from custom_string
to std::string
in the case of the emplace
function (presumably it's using the char*
operator), why can't it do the same when I'm trying to initialize the struct?
In direct initialization you are allowed 1 implicit conversion before the overload resolution of the constructors, as if you are calling a function that takes the same arguments as the constructor.
struct Converted {};
struct Base
{
operator Converted() const { return {}; }
};
struct Final {
Final(Converted) {}
};
void func_converted(Converted) {}
Base base;
func_converted(base); // compiles
Final f{base}; // first converts to Converted then overload resolution of constructors
std::vector<Final> finals;
finals.emplace_back(base); // compiles because it does Final{base} internally
In overload resolution only 1 implicit conversion is allowed between the input type and the argument type, a function taking Final
will have to do Base -> Converted -> Final
which is 2 implicit conversions, not 1.
struct FinalHolder
{
Final f;
};
void func_final(Final) {}
Base base;
FinalHolder h{base}; // error: could not convert 'base' from 'Base' to 'Final'
func_final(base); // error: could not convert 'base' from 'Base' to 'Final'
std::vector<Final> finals;
finals.push_back(base); // doesn't compile because it is same as above
Aggregate initialization is similar to calling a function and follows the same rules, you can trigger direct initialization manually allowing an extra implicit conversion before overload resolution is done.
func_final({base}); // compiles
FinalHolder h{{base}}; // compiles