почему не 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 и т.д.