почему не std :: any_cast поддерживает неявное преобразование?
Почему std::any_cast
std::bad_any_cast
исключение std::bad_any_cast
когда возможно неявное преобразование из фактического сохраненного типа в запрашиваемый тип?
Например:
std::any a = 10; // holds an int now
auto b = std::any_cast<long>(a); // throws bad_any_cast exception
Почему это не разрешено и есть ли способ обхода, чтобы разрешить неявное преобразование (если неизвестен точный тип std::any
hold)?
Ответы
Ответ 1
std::any_cast
указывается в терминах typeid
. Чтобы процитировать cppreference по этому поводу:
std::bad_any_cast
если typeid
запрашиваемого ValueType
не соответствует typeid
содержимого операнда.
Поскольку typeid
не позволяет реализации "выяснить", подразумевается неявное преобразование, нет (насколько мне известно), что any_cast
может это знать.
Иначе говоря, стирание типа, предоставляемое std::any
зависит от информации, доступной только во время выполнения. И эта информация не так богата, как информация, которую компилятор имеет для определения конверсий. Это стоимость стирания типа в С++ 17.
Ответ 2
Чтобы сделать то, что вы хотите, вам понадобится полное отражение и оверенность кода. Это означает, что каждая деталь каждого типа должна быть сохранена в каждом двоичном файле (и каждой подписи каждой функции на каждом типе!) И каждый шаблон в любом месте!), И когда вы попросите конвертировать из любого в тип X, который вы пройдете данные о X в any, которые будут содержать достаточную информацию о типе, который он содержит, чтобы в основном попытаться скомпилировать преобразование в X и потерпеть неудачу или нет.
Существуют языки, которые могут это сделать; каждый двоичный корабль с байтовым кодом IR (или исходным исходным кодом) и интерпретатором/компилятором. Эти языки, как правило, в 2 раза или более медленнее, чем C++, при большинстве задач и имеют значительно больший объем памяти. Возможно, возможно иметь эти функции без этой стоимости, но никто не знает того языка, о котором я знаю.
C++ не обладает этой способностью. Вместо этого он забывает почти все факты о типах во время компиляции. Для любого он запоминает тип, который можно использовать для получения точного соответствия, и как преобразовать его хранилище в указанное точное соответствие.
Ответ 3
std::any
должен быть реализован с типом стиранием. Это потому, что он может хранить любой тип и не может быть шаблоном. В настоящее время в C++ нет других функций для достижения этой цели.
Это означает, что std::any
будет хранить указатель std::any_cast
типа, void*
и std::any_cast
преобразует этот указатель в указанный тип и что он. Он просто выполняет проверку здравомыслия с помощью typeid
прежде чем проверять, является ли тот тип, в который вы его typeid
тот, который хранится в любом.
Разрешить неявные преобразования невозможно с помощью текущей реализации. Подумайте об этом (не typeid
внимания на проверку типа на данный момент).
std::any_cast<long>(a);
a
хранит int
и не long
. Как следует знать std::any
? Он может просто отличить свой void*
от указанного типа, разыскать его и вернуть. Приведение указателя от одного типа к другому является строгим нарушением псевдонимов и приводит к UB, так что плохая идея.
std::any
должен был бы хранить фактический тип хранимого в нем объекта, что невозможно. Вы не можете хранить типы в C++ прямо сейчас. Он может поддерживать список типов вместе со своим соответствующим typeid
и переключать их, чтобы получить текущий тип и выполнить неявное преобразование. Но нет никакого способа сделать это для каждого отдельного типа, который вы собираетесь использовать. Определенные пользователем типы не будут работать в любом случае, и вам придется полагаться на такие вещи, как макросы, чтобы "зарегистрировать" ваш тип и создать для него соответствующий случай переключения 1.
Может быть, что-то вроде этого:
template<typename T>
T any_cast(const any &Any) {
const auto Typeid = Any.typeid();
if (Typeid == typeid(int))
return *static_cast<int *>(Any.ptr());
else if (Typeid == typeid(long))
return *static_cast<long *>(Any.ptr());
// and so on. Add your macro magic here.
// What should happen if a type is not registered?
}
Это хорошее решение? Нет, безусловно. Коммутатор стоит дорого, а мантра C++ - "вы не платите за то, что не используете", так что нет, в настоящее время нет способа добиться этого. Подход также "хакерский" и очень хрупкий (что произойдет, если вы забудете зарегистрировать тип). Короче говоря, возможные выгоды от выполнения подобных действий не стоят проблем в первую очередь.
есть ли временное решение, позволяющее неявное преобразование (в случае, если точный тип, который std :: any hold неизвестен)?
Да, реализуйте std::any
(или сопоставимый тип) и std::any_cast
самостоятельно, используя описанный выше подход к макрорежимам 1. Однако я не буду рекомендовать его. Если вы этого не сделаете и не знаете, какой тип std::any
stores и вам нужен доступ, у вас есть возможный недостаток дизайна.
1: На самом деле не знаю, возможно ли это, я не настолько хорош в использовании макросов (ab). Вы можете также жестко запрограммировать свои типы для своей пользовательской реализации или использовать для этого отдельный инструмент.
Ответ 4
Это можно реализовать, попробовав неявное преобразование непредвиденных ситуаций, если идентификатор типа запрашиваемого типа не совпадает с идентификатором типа хранимого типа. Но это потребует затрат и, следовательно, нарушает принцип "не платить за то, что вы не используете". Другой any
недостаток, например, является невозможность хранить массив.
std::any("blabla");
будет работать, но он сохранит char const*
, а не массив. Вы можете добавить такую функцию в свой собственный пользовательский any
, но тогда вам нужно будет сохранить указатель на строковый литерал, выполнив следующие действия:
any(&*"blabla");
который является нечетным. Решения Стандартного комитета являются компромиссом и не удовлетворить всех, но, к счастью, есть возможность реализовать ваш собственный any
.
Вы также можете расширить any
для хранения, а затем вызывать функции, стираемые стилем, например, но это также не поддерживается стандартом.
Ответ 5
Вместо того, чтобы ожидать этого, вы можете использовать карту функций для определения ваших различных типов.
using anyMap=std::unordered_map<size_t,std::function<void (std::any&,std::ostream &)>>;
Затем установите статику для различных типов, которые вы используете...
size_t myAny::int_type = typeid(int).hash_code();
size_t myAny::str_type = typeid(std::string).hash_code();
И установить статическую функцию карты...
anyMap myAny::out = {
{int_type,[](std::any& a,std::ostream &o) { o << std::any_cast<int>(i); },
{inj_type,[](std::any& j,std::ostream &o) { o << std::any_cast<std::string>(i);}
}
А затем использовать его с любой называемой "вещью"...
if (thing.has_value()) {
myAny::out[thing.type().hash_code()](thing,cout);
}
Ответ 6
вы можете сделать это:
#include <any>
#include <iostream>
#include <cstdlib>
using namespace std;
string getValue(any x)
{
string value;
if(x.type()==typeid(int))
value=to_string(any_cast<int>(x));
else if (x.type()==typeid(double))
value=to_string(any_cast<double>(x));
else
value=any_cast<string>(x);
return value;
}
int main()
{
any x="hello"s;
cout<<getValue(x)<<endl;
x=10;
cout<<getValue(x)<<endl;
x=3.14;
cout<<getValue(x)<<endl;
}
затем вы можете преобразовать строковое значение в int, long и т.д., используя stoi и т.д.