Следующий код успешно компилируется в 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
}
Это включает JSON для современного C ++. Рабочий пример в этот wandbox.
Переменная JSON js
неявно преобразуется в строку. Однако, если я раскомментирую последнюю строку, которая является явным преобразованием, она не будет скомпилирована. Результаты компиляции Вот.
Помимо особых нюансов этой библиотеки json, как вы кодируете класс, чтобы неявное преобразование работало, а явное — нет?
Есть ли какой-то квалификатор конструктора, который допускает такое поведение?
Вот упрощенный код, который воспроизводит ту же проблему:
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
}
Мы видим, что ошибка компиляции похожа:
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;
Проблема зависит от общего S::operator T()
, который с радостью вернет значение любому типу, который вы хотите. Например, назначение s
для любого типа будет работать:
int i = s; // S::operator T() returns int{};
std::string str = s; // S::operator T() returns std::string{};
T
выводится на тип конверсии. В случае std::string
, у него много конструкторов, но если вы сделаете копирования инициализации (1) формы object = other
, T
выводится на тип левого объекта (который std::string
).
Кастинг это другое дело. Видите, это та же проблема, если вы пытаетесь инициализировать копию, используя третью форму (которая в данном случае прямая инициализация):
R r(s); // same ambiguity error
Хорошо, каковы перегрузки конструктора для R
снова?
R() = default;
R(const R&) = default;
R(R&&) = default;
R(int) {}
При условии R
Конструкторы могут взять либо другое R
, или же int
проблема становится очевидной, так как система вывода типа шаблона не знает, какой из них является правильным ответом из-за контекста, из которого вызывается оператор. Здесь прямая инициализация должна учитывать все возможные перегрузки. Вот основное правило:
является типом, который требуется в результате преобразования. п тип возврата шаблона функции преобразования
В этом случае:
R r = s;
R
является типом, который требуется в результате преобразования (). Тем не менее, вы можете сказать, какой тип будет представлять в следующем коде?
R r(s);
Теперь контекст имеет R
а также int
в качестве параметров, потому что в R есть конструктор, который принимает целые числа. Но тип преобразования должен быть выведен только для одного из них. R
является действительным кандидатом, так как есть по крайней мере один конструктор, который принимает R
, int
также является допустимым кандидатом, поскольку есть конструктор, принимающий также целое число. Нет победителя, поскольку оба они одинаково действительны, отсюда и неоднозначность.
Когда вы приведете свой объект JSON к std::string
, ситуация точно такая же. Есть конструктор, который принимает строку, и есть другой, который принимает распределитель. Обе перегрузки допустимы, поэтому компилятор не может выбрать одну.
Проблема исчезла бы, если бы оператор преобразования был помечен как explicit
, Это означает, что вы сможете сделать std::string str = static_cast<std::string>(json)
Однако вы теряете способность неявно конвертировать его как std::string str = json
,
Я думаю, что когда вы используете явное преобразование, компилятору приходится выбирать из большего количества функций, чем когда код использует неявное преобразование.
Когда найден компилятор
string s1 = js
он исключает из перегрузки все конструкторы и преобразования, помеченные как «явные», поэтому в результате выбирается одна функция.
Вместо этого, когда компилятор нашел:
string s2 = (string)js ;
оно должно включать в себя все преобразования, а затем двусмысленность.