The following code compiles successfully in C++11:
#include "json.hpp"
using json = nlohmann::json ;
using namespace std ;
int main(){
json js = "asd" ;
string s1 = js ; // <---- compiles fine
//string s2 = (string)js ; // <---- does not compile
}
It includes JSON for Modern C++. A working example is in this wandbox.
The JSON variable js
is implicitly converted to a string. However, if I uncomment the last line, which is an explicit conversion, it fails to compile. Compilation results here.
Beyond the particular nuances of this json library, how do you code a class so that an implicit conversion works but an explicit one does not?
Is there some kind of constructor qualifier that allows this behavior?
Implicit type conversion in C language is the conversion of one data type into another datatype by the compiler during the execution of the program. It is also called automatic type conversion.
Implicit conversions allow the compiler to treat values of a type as values of another type. There's at least one set of scenarios in which this is unambiguously bad: non-total conversions. That is, converting an A to a B when there exists A s for which this conversion is impossible.
We cannot perform implicit type casting on the data types which are not compatible with each other such as: Converting float to an int will truncate the fraction part hence losing the meaning of the value. Converting double to float will round up the digits.
"It is possible for implicit conversions to lose information, signs can be lost (when signed is implicitly converted to unsigned), and overflow can occur (when long long is implicitly converted to float)."
Here's a simplified code that reproduces the same issue:
struct S
{
template <typename T>
operator T() // non-explicit operator
{ return T{}; }
};
struct R
{
R() = default;
R(const R&) = default;
R(R&&) = default;
R(int) {} // problematic!
};
int main()
{
S s{};
R r = static_cast<R>(s); // error
}
We can see the compile error is similar:
error: call of overloaded 'R(S&)' is ambiguous
R r = static_cast<R>(s);
^
note: candidates...
R(int) {}
R(R&&) = default;
R(const R&) = default;
The problem relies on the generic S::operator T()
, which will happily return a value to whatever type you want. For example, assigning s
to any type will work:
int i = s; // S::operator T() returns int{};
std::string str = s; // S::operator T() returns std::string{};
T
is deduced to the conversion type. In the case of std::string
, it has a lot of constructors, but if you do a copy-initialization(1) of the form object = other
, T
is deduced to the left-hand object's type (which is std::string
).
Casting is another matter. See, it's the same problem if you try to copy-initialize using the third form (which in this case is a direct initialization):
R r(s); // same ambiguity error
Okay, what are the constructor overloads for R
again?
R() = default;
R(const R&) = default;
R(R&&) = default;
R(int) {}
Given that R
's constructors can take either another R
, or int
, the problem becomes apparent, as the template type deduction system doesn't know which one of these is the correct answer due to the context in which the operator is called from. Here, direct initialization has to consider all the possible overloads. Here's the basic rule:
A is the type that is required as the result of the conversion. P is the return type of the conversion function template
In this case:
R r = s;
R
is the type that is required as the result of the conversion (A). However, can you tell which type A will represent in the following code?
R r(s);
Now the context has R
and int
as options, because there is a constructor in R that takes integers. But the conversion type needs to be deduced to only one of them. R
is a valid candidate, as there is at least one constructor which takes an R
. int
is a valid candidate as well, as there is a constructor taking an integer too. There is no winner candidate, as both of them are equally valid, hence the ambiguity.
When you cast your json object to an std::string
, the situation is exact the same. There is a constructor that takes an string, and there is another one that takes an allocator. Both overloads are valid, so the compiler can't select one.
The problem would go away if the conversion operator were marked as explicit
. It means that you'd be able to do std::string str = static_cast<std::string>(json)
, however you lose the ability to implicitly convert it like std::string str = json
.
I think it is that when you use explicit conversion, the compiler have to choose from more function that when the code use implicit conversion. When compiler found
string s1 = js
it exclude from overloading, all costructor and conversion marked "explicit" so it results to pick one function. Instead when compiler found :
string s2 = (string)js ;
it must include all conversion and then the ambiguity.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With