std :: any без RTTI, как это работает?

Если я хочу использовать std::any я могу использовать его с отключенным RTTI. Следующий пример компилируется и выполняется, как ожидалось, также с -fno-rtti с gcc.

int main()
{   
    std::any x;
    x=9.9;
    std::cout << std::any_cast<double>(x) << std::endl;
}

Но как std::any хранит информацию о типе? Как я вижу, если я вызываю std::any_cast с "неправильным" типом, я получил исключение std::bad_any_cast как и ожидалось.

Как это реализовано или это может быть только функция gcc?

Я обнаружил, что boost::any также не нуждался в RTTI, но я также не нашел, как это решить. Повышает ли какая-либо потребность RTTI? ,

Копание в заголовок STL не дает мне никакого ответа. Этот код почти не читается для меня.

Ответы

Ответ 1

TL; DR; std::any содержит указатель на статическую функцию-член шаблонного класса. Эта функция может выполнять множество операций и специфична для данного типа, поскольку фактический экземпляр функции зависит от аргументов шаблона класса.


Реализация std::any в libstdc++ не такая сложная, вы можете посмотреть на нее:

https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/any

В принципе, std::any имеет две вещи:

  • Указатель на (динамически) выделенное хранилище;
  • Указатель на функцию диспетчера хранилища:
void (*_M_manager)(_Op, const any*, _Arg*);

Когда вы создаете или назначаете новый std::any с объектом типа T, _M_manager указывает на функцию, специфичную для типа T (которая на самом деле является статической функцией-членом класса, специфичного для T):

template <typename _ValueType, 
          typename _Tp = _Decay<_ValueType>,
          typename _Mgr = _Manager<_Tp>, // <-- Class specific to T.
          __any_constructible_t<_Tp, _ValueType&&> = true,
          enable_if_t<!__is_in_place_type<_Tp>::value, bool> = true>
any(_ValueType&& __value)
  : _M_manager(&_Mgr::_S_manage) { /* ... */ }

Поскольку эта функция специфична для данного типа, вам не требуется RTTI для выполнения операций, требуемых std::any.

Кроме того, легко проверить, что вы производите правильный тип в std::any_cast. Вот ядро реализации gcc std::any_cast:

template<typename _Tp>
void* __any_caster(const any* __any) {
    if constexpr (is_copy_constructible_v<decay_t<_Tp>>) {
        if (__any->_M_manager == &any::_Manager<decay_t<_Tp>>::_S_manage) {
            any::_Arg __arg;
            __any->_M_manager(any::_Op_access, __any, &__arg);
            return __arg._M_obj;
        }
    }
    return nullptr;
}

Вы можете видеть, что это просто проверка равенства между хранимой функцией внутри объекта, который вы пытаетесь выполнить (_any->_M_manager), и функцией менеджера типа, который вы хотите наложить (&any::_Manager<decay_t<_Tp>>::_S_manage).


Класс _Manager<_Tp> на самом деле является псевдонимом либо _Manager_internal<_Tp> либо _Manager_external<_Tp> зависимости от _Tp. Этот класс также используется для размещения/построения объекта для std::any класса.

Ответ 2

Одним из возможных решений порождают уникальный идентификатор для каждого типа, возможно, хранится в any (я предполагаю, что Вы знаете, как большеменьше any внутренне работает). Код, который может это сделать, может выглядеть примерно так:

struct id_gen{
    static int &i(){
        static int i = 0;
        return i;
    }

    template<class T>
    struct gen{
        static int id() {
            static int id = i()++;
            return id;
        }
    };    
};

С помощью этой функции вы можете использовать идентификатор типа вместо RTTI typeinfo чтобы быстро проверить тип.

Обратите внимание на использование статических переменных внутри функций и статических функций. Это делается для того, чтобы избежать проблемы неопределенного порядка инициализации статической переменной.

Ответ 3

Ручная реализация ограниченного RTTI не так уж трудна. Вам понадобятся статические общие функции. Я могу сказать это без полной реализации. здесь есть одна возможность:

class meta{
    static auto id(){
        static std::atomic<std::size_t> nextid{};
        return ++nextid;//globally unique
    };
    std::size_t mid=0;//per instance type id
public:
    template<typename T>
    meta(T&&){
        static const std::size_t tid{id()};//classwide unique
        mid=tid;
    };
    meta(meta const&)=default;
    meta(meta&&)=default;
    meta():mid{}{};
    template<typename T>
    auto is_a(T&& obj){return mid==meta{obj}.mid;};
};

Это мое первое наблюдение; далеко не идеальный, не хватает многих деталей. Можно использовать один экземпляр meta как нестатический член данных его предполагаемой реализации std::any.