一个 unordered_map 返回不同类型的对 C++

问题描述

我正在尝试实现一个返回 std::unordered_mapdoubleint 对的 std::string。地图的键是 std::string。以下是我迄今为止尝试过的:

#include <fstream>
#include <iostream>
#include <string>
#include <sstream>
#include <unordered_map>
#include <utility>
#include <vector>

// A base class for boundary class
class Boundbase {
    public:
    Boundbase(){};
    virtual ~Boundbase(){};
};

// A different map of boundaries for each different data type
template <class dType>
class Boundary : public Boundbase {
    std::pair<dType,dType> bpair;
    
    public:
    //Constructor
    Boundary(const std::string &lbound,const std::string &ubound) {
        setbound(lbound,ubound);
    };

    //A method to set boundary pair
    void setbound(const std::string &lbound,const std::string &ubound);
    
    // A method to get boundary pair
    std::pair<dType,dType> getbound() {return bpair;}
};

// Class to hold the different boundaries 
class Boundaries {
    std::unordered_map<std::string,Boundbase*> bounds;

    public:
    //Constructor
    Boundaries() {};

    // A method to set boundary map
    void setboundmap(std::unordered_map<std::string,std::vector<std::string>> xtb);

    // A template to get boundaries.
    std::unordered_map<std::string,Boundbase*> getbounds()
        {return bounds;}
};

// A method to set covariate boundary
template <class dType> void
Boundary<dType>::setbound(const std::string &lbound,const std::string &ubound) {
    dType val;
    std::istringstream isa(lbound);
    while(isa >> val) {
        bpair.first = val;
    }
    std::istringstream isb(ubound);
    while(isb >> val) {
        bpair.second = val;
    }
}

// A method to set boundary map
void Boundaries::setboundmap(std::unordered_map<std::string,std::vector<std::string>> xtb) {
    for(auto s : xtb) {
        char type = s.second[1][0];
        switch(type) {
            case 'd': {
            std::pair<std::string,Boundbase*> opair;
            opair.first = s.first;
            opair.second = new Boundary<double>(
                    s.second[2],s.second[3]);
            bounds.insert(opair);
            }
            break;
            case 'i': {
            std::pair<std::string,Boundbase*> opair;
            opair.first = s.first;
            opair.second = new Boundary<int>(
                    s.second[2],s.second[3]);
            bounds.insert(opair);
            break;
            }
            case 'c': {
            std::pair<std::string,Boundbase*> opair;
            opair.first = s.first;
            opair.second = new Boundary<std::string>(
                    s.second[2],s.second[2]);
            bounds.insert(opair);
            break;
            }
        }
    }
}

这可以使用 g++ 编译。当我尝试运行它时(如下):

int main() {
    Data D;
    Boundaries B;
    std::ifstream iss("tphinit.txt");
    D.read_lines(iss);

    auto dbounds = D.get_xtypebound();
    B.setboundmap(dbounds);

    auto tbounds = B.getbounds();
    auto sbound = tbounds["X1"];
    std::cout << sbound->bpair.first << "," 
        << sbound->bpair.second << std::endl;
}

我得到 'class Boundbase' has no member named 'bpair' 是真的,因为我指向的是基类而不是派生类。据我所知,尝试获取生成bpair 需要我使用访问者模式。现在,很明显我是菜鸟,所以当我看到不同的做这件事的方法时,我有点不知所措(没有反思作者,只是因为我缺乏经验)。

所以我的主要问题是:这是最好和最简单的方法吗?如果可能的话,我想避免 boost::variant(主要是为了纯度:这不会那么困难)。一个子问题是我是否必须使用访问者模式,或者是否有更好/更简单的方法获取成员 pbair

我将不得不多次执行此查找,因此我希望尽可能快,但为了简单起见使用 stl

解决方法

使您的值在 3 种类型上成为标准变体。

如果失败,则提升变体。

Std 和 boost 变体确实是您想要的。您最终将实现其实现的某些子集。

如果做不到这一点,请查找有关如何实现其中一个的教程,或使用 std any。如果做不到这一点,则动态转换其他无用的包装器类型,并将虚拟 dtor 存储在唯一的 ptr 中,或者使用 try get 方法进行手动 RTTI。

然而,这只会变得越来越丑陋和/或效率低下。

Boost 变体和它的 std 变体是有原因的,这个原因是为了以有效的方式解决您所描述的确切问题。

#include <tuple>
#include <utility>
#include <string>

template<class...Ts>
struct destroy_helper {
    std::tuple<Ts*...> data;
    destroy_helper( std::tuple<Ts*...> d ):data(d){}
    template<class T>
    static void destroy(T* t){ t->~T(); }
    template<std::size_t I>
    void operator()(std::integral_constant<std::size_t,I>)const {
        destroy( std::get<I>( data ) );
    }
};

struct construct_helper {
    template<class T,class...Args>
    void operator()(T* target,Args&&...args)const {
        ::new( (void*)target ) T(std::forward<Args>(args)...);
    }
};

template<std::size_t...Is>
struct indexes {};

template<std::size_t N,std::size_t...Is>
struct make_indexes:make_indexes<N-1,N-1,Is...> {};

template<std::size_t...Is>
struct make_indexes<0,Is...>{
    using type=indexes<Is...>;
};
template<std::size_t N>
using make_indexes_t = typename make_indexes<N>::type;

template<class F>
void magic_switch( std::size_t i,indexes<>,F&& f ) {}

template<std::size_t I0,std::size_t...Is,class F>
void magic_switch( std::size_t i,indexes<I0,Is...>,F&& f )
{
    if (i==I0) {
        f( std::integral_constant<std::size_t,I0>{} );
        return;
    }
    magic_switch( i,indexes<Is...>{},std::forward<F>(f) );
}

template<class T0>
constexpr T0 max_of( T0 t0 ) {
    return t0;
}
template<class T0,class T1,class...Ts>
constexpr T0 max_of( T0 t0,T1 t1,Ts... ts ) {
    return (t1 > t0)?max_of(t1,ts...):max_of(t0,ts...);
}

template<class...Ts>
struct Variant{
  using Data=typename std::aligned_storage< max_of(sizeof(Ts)...),max_of(alignof(Ts)...)>::type;
  std::size_t m_index=-1;
  Data m_data;
  template<std::size_t I>
  using alternative_t=typename std::tuple_element<I,std::tuple<Ts...>>::type;
  using pointers=std::tuple<Ts*...>;
  using cpointers=std::tuple<Ts const*...>;

  template<class T> T& get(){ return *reinterpret_cast<T*>(&m_data); }
  template<class T> T const& get() const { return *reinterpret_cast<T*>(&m_data); }
  template<std::size_t I>
  alternative_t<I>& get(){ return std::get<I>(get_pointers()); }
  template<std::size_t I>
  alternative_t<I> const& get()const{ return std::get<I>(get_pointers()); }

  pointers get_pointers(){
    return pointers( (Ts*)&m_data... );
  }
  cpointers get_pointers()const{
    return cpointers( (Ts const*)&m_data... );
  }
  std::size_t alternative()const{return m_index;}

  void destroy() {
      if (m_index == -1)
        return;
      magic_switch(m_index,make_indexes_t<sizeof...(Ts)>{},destroy_helper<Ts...>(get_pointers()));
  }

  template<std::size_t I,class...Args>
  void emplace(Args&&...args) {
      destroy();
      construct_helper{}( std::get<I>(get_pointers()),std::forward<Args>(args)... );
      m_index = I;
  }

  Variant()=default;

  Variant(Variant const&)=delete;//todo
  Variant&operator=(Variant const&)=delete;//todo
  Variant(Variant &&)=delete;//todo
  Variant&operator=(Variant &&)=delete;//todo

  ~Variant(){destroy();}
};

int main() {
    Variant<int,double,std::string> bob;
    bob.emplace<0>( 7 );
    bob.emplace<1>( 3.14 );
    bob.emplace<2>( "hello world" );
}

here is a really simple variant interface

困难的部分是将运行时索引转换为您想要使用的编译时索引。我称之为魔法开关问题。

您可能还想实现应用访问者。

...

或者...

template<class T>
struct Derived;
struct Base {
  virtual ~Base() {}
  template<class T>
  friend T* get(Base* base) {
    Derived<T>* self = dynamic_cast<T*>(base);
    return self?&self.t:nullptr;
  }
  template<class T>
  friend T const* get(Base const* base) {
    Derived<T> const* self = dynamic_cast<T const*>(base);
    return self?&self.t:nullptr;
  }
};
template<class T>
struct Derived:Base {
  Derived(T in):t(std::move(in)){}
  T t;
};

std::unordered_map<std::string,std::unique_ptr<Base>> map;

map["hello"] = std::unique_ptr<Base>( new Derived<int>(-1) );
map["world"] = std::unique_ptr<Base>( new Derived<double>(3.14) );

int* phello = get<int>(map["hello"]);
if (phello) std::cout << *hello << "\n";
double* pworld = get<double>(map["world"]);
if (pworld) std::cout << *world << "\n";

这是一个非常讨价还价的基础std::any