Удаление типа для объектов, содержащих std ::tuple в c 11

#c #c 11

#c #c 11

Вопрос:

Допустим, у меня есть универсальный класс Container, который содержит кортеж любого типа и имеет функцию template<typename T> Tamp; get<T>(); , которая возвращает ссылку на элемент в кортеже. Моя очень простая реализация выглядит следующим образом:

 template<typename... Ts>
class Container
{
    std::tuple<Ts...> contents;

    public:
    Container(const Tsamp;... ts) : contents(ts...) {}

    template <typename T>
    Tamp; get()
    {
        //TypeIndex is some meta-programming struct to find index of T in Ts
        return std::get<TypeIndex<T, Ts...>::value>(contents);
    }
};
  

Существуют ли какие-либо хорошие методы удаления типа, позволяющие превратить контейнер в обычный класс без изменения сигнатуры функции get? Как при вызове get<T>() без знания полного списка типов кортежей? Что-то вроде этого:

 Struct A { int x; }
Struct B { int y; }
Struct C { int z; }

int main()
{
    Container container(A(), B()); //Underlying storage is a std::tuple<A, B>

    Aamp; a = container.get<A>(); //Doesn't know the tuples type list but assumes A is in there.

    Camp; c = container.get<C>(); //C isn't in the tuples type list, crash program, which would be correct behavior.

}
  

boost::any это обычное решение для решения проблем такого типа, но не решает эту конкретную проблему, потому что мне нужно было бы знать фактический тип базового кортежа для приведения. Например, если бы я попытался использовать это в примере выше, я бы сделал boost::any_cast<std::tuple<A, B>> , чтобы получить A или B, которые мне бесполезны, потому что я намеренно пытаюсь скрыть список типов кортежей.

Редактировать: полное определение TypeIndex.

 #include <type_traits>

template <typename T, typename... Ts>
struct TypeIndex;

template <typename T, typename... Ts>
struct TypeIndex<T, T, Ts...> : std::integral_constant<std::size_t, 0> {};

template <typename T, typename U, typename... Ts>
struct TypeIndex<T, U, Ts...> : std::integral_constant<std::size_t, 1   TypeIndex<T, Ts...>::value> {};
  

Комментарии:

1. Прав ли я, предполагая, TypeIndex что функция определена вами? Это constexpr ?

2. Да, TypeIndex — это определенная мной структура, производная от std::intergral_constant , которая получает индекс типа в списке типов, это вспомогательная структура для std::get<N>();

3. Итак, если это так, constexpr то не могли бы вы попробовать std::enable_if с get() функцией? Кроме того, как ваша TypeIndex обработка std::tuple<int, int> ? (Редактировать: я сказал function в предыдущем комментарии, я имел в виду struct .)

4. Для std::tuple<int, int> он вернул бы первый int в списке и не смог бы получить второй, что нормально в моей ситуации, потому что предполагается, что у кортежа есть уникальный список.

5. Тогда не можете ли вы использовать std::enable_if для превращения template <typename T> Tamp; get() в функцию, которая не компилируется, если вы вызываете с недопустимым типом? (См.: en.cppreference.com/w/cpp/types/enable_if )

Ответ №1:

Вместо рукописных TypeIndex<T, Ts...>::value вы можете использовать typeid(T)::hash_code() и хранить данные в std::unordered_map<size_t, boost::any> .

std::tuple не сохраняет информацию о базовых типах. Эта информация закодирована в типе кортежа. Итак, если ваш get метод не может знать тип кортежа, то он не может получить смещение в нем, где хранится значение. Итак, вам нужно вернуться к динамическим методам, и наличие map является самым простым из них.

Комментарии:

1. Ваше предложение превращает конструкцию времени компиляции в конструкцию времени выполнения, вероятно, не то, что хотел OP? Хотя я не знаю, для чего предназначался OP.

2. OP хочет контейнер с полиморфизмом. В C есть статический и динамический полиморфизм. OP попросил включить контейнер в обычный класс, а не шаблон. Поэтому мы должны использовать динамический полиморфизм.

3. Смотрите мой ответ, похоже, это делается во время компиляции.

4. В вашем ответе контейнер по-прежнему является шаблоном и имеет другой тип в зависимости от сохраненных значений. Таким образом, тип кортежа не стирается.

5. Это правильный способ сделать это — я неправильно прочитал сообщение OP . 🙂

Ответ №2:

Несколько более эффективное решение, чем предложенные до сих пор, заключается в использовании std::tuple в качестве фактического базового хранилища, что позволяет избежать использования any или unordered_map

Если мы используем классический шаблон удаления типа, нам нужно только одно динамическое выделение (плюс все, что требуется для копирования реальных объектов), или ноль, если вы реализуете оптимизацию небольшого буфера.

Мы начнем с определения базового интерфейса для доступа к элементу по типу.

 struct base
{
    virtual ~base() {}

    virtual void * get( std::type_info const amp; ) = 0;
};
  

Мы используем void* вместо any для возврата ссылки на объект, таким образом избегая копирования и, возможно, выделения памяти.

Фактический класс хранилища является производным от base и шаблонизирован на основе аргументов, которые он может содержать:

 template<class ... Ts>
struct impl : base
{
    template<class ... Us>
    impl(Us amp;amp; ... us) : data_(std::forward<Us>(us) ... ) 
    {
        //Maybe check for duplicated types and throw.
    }

    virtual void * get( std::type_info const amp; ti ) 
    {
        return get_helper( ti, std::index_sequence_for<Ts...>() );
    }

    template<std::size_t ... Indices>
    void* get_helper( std::type_info const amp; ti, std::index_sequence<Indices...> )
    {
        //If you know that only one element of a certain type is available, you can refactor this to avoid comparing all the type_infos
        const bool valid[] = { (ti == typeid(Ts)) ... };

        const std::size_t c = std::count( std::begin(valid), std::end(valid), true );
        if ( c != 1 )
        {
            throw std::runtime_error(""); // something here
        }

        // Pack the addresses of all the elements in an array
        void * result[] = { static_cast<void*>(amp; std::get<Indices>(data_) ) ... };

        // Get the index of the element we want
        const int which = std::find( std::begin(valid), std::end(valid), true ) - std::begin(valid);

        return result[which];
    }

    std::tuple<Ts ... > data_;
};
  

Теперь нам остается только обернуть это в типобезопасную оболочку:

 class any_tuple
{
public:
     any_tuple() = default; // allow empty state

     template<class ... Us>
     any_tuple(Us amp;amp; ... us) :
            m_( new impl< std::remove_reference_t< std::remove_cv_t<Us> > ... >( std::forward<Us>(us) ... ) )
       {}

     template<class T>
     Tamp; get()
     {
        if ( !m_ )
        {
            throw std::runtime_error(""); // something
        }
        return *reinterpret_cast<T*>( m_->get( typeid(T) ) );
     }

     template<class T>
     const Tamp; get() const
     {
         return const_cast<any_tupleamp;>(*this).get<T>();
     }

     bool valid() const { return bool(m_); }

 private:
     std::unique_ptr< base > m_; //Possibly use small buffer optimization
 };
  

Проверьте это в реальном режиме.

Это может быть расширено многими способами, например, вы можете добавить конструктор, который принимает фактический кортеж, вы можете получить доступ по индексу и упаковать значение в std::any и т.д.

Ответ №3:

 #include <iostream>

struct tuple_base {
    virtual ~tuple_base() {}
};

template <typename T>
struct leaf : virtual tuple_base {
    leaf(T const amp; t) : value(t) {}

    virtual ~leaf() {}

    T value;
};

template <typename ... T>
struct tuple : public leaf<T> ... {
    template <typename ... U>
    tuple(U amp;amp; ... u) : leaf<T>{static_cast<Uamp;amp;>(u)} ... {}
};

struct container {  
    tuple_base* erased_value;

    template <typename T>
    T amp; get() {
        return dynamic_cast<leaf<T>*>(erased_value)->value;
    }
};

int main() {
    container c{new tuple<int, float, char>{1, 1.23f, 'c'}};

    std::cout << c.get<float>() << std::endl;
}
  

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

Комментарии:

1. Я не думаю, что вам нужно virtual это реализовывать.

2. используйте = default для деструктора. Это имеет некоторые преимущества, одно из которых заключается в том, что делает тип тривиально разрушаемым, если применимо.

3. Потому что я написал это за 3 минуты. Как я уже сказал, это доказательство концепции … если кто-то действительно хочет использовать это для чего-то, очевидно, что это нужно будет очистить

4. Я полагаю, что я уберу это для всех!

5. @pat тогда, пожалуйста, сделайте, потому что в остальном это отличное решение.

Ответ №4:

Если вы согласны с использованием boost::any , вы могли бы использовать vector или unordered_map из них. Вот версия, реализованная с unordered_map :

 class Container
{
public:
    template<typename... Ts>
    Container(std::tuple<Ts...>amp;amp; t)
    {
        tuple_assign(std::move(t), data, std::index_sequence_for<Ts...>{});
    }

    template<typename T>
    T get()
    {
        auto it = data.find(typeid(T));

        if(it == data.cend()) {
            throw boost::bad_any_cast{};
        } else {
            return boost::any_cast<T>(it->second);
        }
    }

private:
    std::unordered_map<std::type_index, boost::any> data;
};
  

И тогда вы могли бы написать почти так же, как в вашем запросе. Я изменил конструктор, чтобы он принимал кортеж, чтобы избежать множества sfinae-кода для предотвращения переопределения конструкторов копирования / перемещения, но вы можете сделать это, если пожелаете.

 Container c(std::make_tuple(1, 1.5, A{42}));

try {
    std::cout << "int: " << c.get<int>() << 'n';
    std::cout << "double: " << c.get<double>() << 'n';
    std::cout << "A: " << c.get<A>().val << 'n';
    c.get<Aamp;>().val = 0;
    std::cout << "A: " << c.get<A>().val << 'n';
    std::cout << "B: " << c.get<B>().val << 'n'; // error
} catch (boost::bad_any_cast constamp; ex) {
    std::cout << "exception: " << ex.what() << 'n';
}
  

живая демонстрация

Вы также могли бы указать вашему устройству Container зафиксировать std::terminate() вместо создания исключения.

Комментарии:

1. Почему за меня проголосовали с понижением? Это соответствует интерфейсу в соответствии с запросом.

2. Я не уверен, что моей кодовой базе прямо сейчас требуется кортеж, но этот метод хорош для возврата, если нет возможного решения.