Возврат шаблонного типа из контейнера разнородных шаблонных значений

Я помещаю экземпляры шаблонного класса, содержащего разнородные значения шаблона, в контейнер, например std :: vector. При доступе к конкретному элементу из указанного вектора я требую, чтобы возвращался точный шаблонный тип, а не только базовый указатель WrapperBase*. Я пробовал использовать boost :: any, boost :: variant и базовый класс / шаблонный дочерний класс, как показано ниже. Решение во время компиляции было бы идеальным (возможно, какое-то волшебство мета-шаблонов); в противном случае было бы достаточно решения на основе RTTI.

В идеальном мире я мог бы сделать что-то вроде этого:

vector< WrapperBase > v;
v.push_back( Wrapper<int>{1} );
v.push_back( Wrapper<float>{3.14f} );
auto v = vector[0].get(); //typeid( decltype( v) ).name() would be "int"
auto v = vector[1].get(); //typeid( decltype( v) ).name() would be "float"

Вариант Boost почти предоставил решение; однако я могу получить доступ только к определенному типу в operator()() методе и не могу его вернуть. Тип будет использоваться другими функциями, поэтому я не могу заставить метод посетителя выполнять желаемую работу.

Этот код доступен http://coliru.stacked-crooked.com/a/5a44681322cdbd77

#include <iostream>
#include <type_traits>
#include <vector>
#include <boost/variant.hpp>
#include <boost/any.hpp>

struct WrapperBase
{
   template< typename T >
   T* get();
};

template< typename T >
struct Wrapper : WrapperBase
{
   Wrapper( T* t ) : WrapperBase(), _val( t ) {}
   T* get() { return _val; }
   T* _val;
};

template< typename T >
T* WrapperBase::get()
{
   return static_cast<Wrapper<T>>( this )->get();
}

struct Visitor : boost::static_visitor<int> //sadly, we can only return ints :(.
{
   int operator()( Wrapper<int>& b ) const
   {
      auto bType = *b.get();
      std::cout << "b type is " << typeid( decltype( bType ) ).name() << "... Val is " << bType << "...\n";
      return 2;
   }

   int operator()( Wrapper<float>& b ) const
   {
      auto bType = *b.get();
      std::cout << "b type is " << typeid( decltype( bType ) ).name() << "... Val is " << bType << "...\n";
      return 6;
   }

   int operator()( Wrapper<std::string>& b ) const
   {
      auto bType = *b.get();
      std::cout << "b type is " << typeid( decltype( bType ) ).name() << "... Val is " << bType << "...\n";
      return 12345;
   }

};

int main( int argc, char* args[] )
{
   std::cout << "Hello World...\n";

   int ia = 5;
   Wrapper<int> a{ &ia };

   float fb = 3.1415f;
   Wrapper<float> b{ &fb };

   std::string s = "testing this...\n";
   Wrapper<std::string> c{ &s };

   auto aType = *a.get(); //aType is "int"
   std::cout << "a type is " << typeid( decltype( aType ) ).name() << "... Val is " << aType << "...\n";

   auto bType = *b.get(); //aType is "float"
   std::cout << "b type is " << typeid( decltype( bType ) ).name() << "... Val is " << bType << "...\n";

   auto cType = *c.get(); //aType is "std::string"
   std::cout << "c type is " << typeid( decltype( cType ) ).name() << "... Val is " << cType << "...\n";

   std::vector< boost::any > vec;

   vec.push_back( &a );
   vec.push_back( &b );
   vec.push_back( &c );

   //for( int i = 0; i < vec.size() ; i++ )
   //{
   //In my actual code, I have no way of knowing I need to cast to <int> and therefore cannot cast.
   //   auto t = boost::any_cast<Wrapper<int>*>( vec[i] );
   //   std::cout << "[" << i << "] type is " << typeid( decltype( t ) ).name() << "... Val is " << t << "...\n";    
   //}    
   std::cout << "...\n...\n...\n";

   using VAR = boost::variant< Wrapper<int>, Wrapper<float>, Wrapper<std::string> >;

   std::vector< VAR > myVar;
   myVar.push_back( a );
   myVar.push_back( b );
   myVar.push_back( c );
   for (int i = 0; i < myVar.size() ; i++)
   {
      auto v = myVar[i];
      //apply_visitor is VERY close to what I want - I have precise type, but only within operator()()
      std::cout << boost::apply_visitor( Visitor(), v ) << "\n";
   }

   std::cout << "...\n...\n...\n";

   std::vector< WrapperBase > v;
   v.push_back( a );
   v.push_back( b );
   v.push_back( c );

   //for (int i = 0; i < v.size() ; i++)
   //{
   //   auto t = v[i].get<int>(); //same problem as boost::any, but instead I have pointer to base class
   //   std::cout << "["<<i<<"] type is " << typeid( decltype( t )).name() << "... Val is " << t << "...\n";
   //   
   //}


   return 0;
}

person Scott Nykl    schedule 28.06.2017    source источник
comment
std::vector однороден по определению (даже если его тип значения является вариантом, который стирает фактическое содержимое по типу). Таким образом, вы не можете вытащить тип обратно. Единственный разнородный контейнер (или контейнер, поскольку он не соответствует требованиям Container) - это std::tuple.   -  person Quentin    schedule 28.06.2017
comment
См. Также gieseanw.wordpress.com/2017. / 05/03 /, это была одна из самых интересных статей о C ++, которые я читал, кажется немного актуальной, хотя и не решает вашу проблему.   -  person Curious    schedule 28.06.2017


Ответы (1)


В этой замечательной статье рассказывается о том, как шаблон посетителя можно использовать с базовыми объектами с удаленным типом, такими как std::any https://gieseanw.wordpress.com/2017/05/03/a-true-heterogener-container-in-c/, поэтому, если вы используете обсуждаемые концепции теоретически вы можете использовать шаблон посетителя с однородным контейнером.


Ваша цель получить такой код

// typeid(val_one).name() is 
auto val_one = homogenous_container[runtime_value_one]; 
// typeid(val_two).name() is `double`
auto val_two = homogenous_container[runtime_value_two]; 

не может быть достигнуто, auto вывод типа происходит во время компиляции, нет способа привязать его к условию выполнения и изменить тип на основе этого.

Самое близкое к этому - использовать std::variant и шаблон посетителя. Вы не можете привязать выведенный во время компиляции тип к произвольному типу времени выполнения. Это просто разные концепции, которые существуют в разное время в среде компиляции / выполнения C ++.

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

person Curious    schedule 28.06.2017
comment
Я польщен :-). Одна вещь, которую я хотел бы добавить (и, возможно, должен отредактировать сообщение, чтобы быть более точным), заключается в том, что производительность посещения гетерогенного контейнера полностью снижает производительность std :: any (из-за всех продолжающихся any_cast). - person AndyG; 05.08.2017