lambdaboost-variantboost-multi-indexextractorapply-visitor

Is it possible to create Boost multi_index MEM_FUN key extractors for a container of Boost variant?


I am attempting to implement a multi_index container of Boost::variant objects. The variant consists of two derived classes of one common base object. I have implemented a virtual function in each derived class ("extractKey()") which returns a std::pair<char,char> to provide a suitable key value regardless of which derived object occupies the variant.

How do I use apply_visitor() (perhaps in a lambda expression?) to invoke the "extractKey()" function as a CONST_MEM_FUN key extractor in order to obtain the key value? I have not been able to get the syntax correct in order to accomplish this.

I am using Visual Studio 2019 and C++17.

Edit: While I already have a much more sane and conventional solution which simply uses a container of base object pointers and virtual functions in the derived objects (and no variants!) other scenarios arise where there's a need to store fundamentally different objects (not derived from a common base object) in a multi_index container. That's the real reason why I'm hoping to find a solution to the question posed here.


Solution

  • The best way to approach this is to provide your own user-defined key extractor. Note that the fact that the variant types are derived from a common base does not play any significant role here: in a scenario without inheritance, simply apply a regular visitor that takes care of all the types in the variant (possibly with a generic lambda if all the types conform to the same syntax for getting the key).

    Live Coliru Demo

    #include <boost/multi_index_container.hpp>
    #include <boost/multi_index/ordered_index.hpp>
    #include <boost/variant/variant.hpp>
    #include <utility>
    
    struct base
    {
      virtual ~base()=default;
      virtual std::pair<char,char> extractKey()const=0;
    };
    
    struct derived1:base
    {
      std::pair<char,char> extractKey()const override{return{1,0};};
    };
    
    struct derived2:base
    {
      std::pair<char,char> extractKey()const override{return{0,1};};
    };
    
    using namespace boost::multi_index;
    
    using variant=boost::variant<derived1,derived2>;
    
    struct variant_key
    {
      using result_type=std::pair<char,char>;
      
      auto operator()(const variant& x)const
      {
        return boost::apply_visitor(
          [](const base& b){return b.extractKey();},
          x
        );
      }
    };
    
    using container=multi_index_container<
      variant,
      indexed_by<
        ordered_non_unique<variant_key>
      >
    >;
    
    // testing
    
    #include <iostream>
    
    template<typename... Ts> struct overloaded:Ts...{using Ts::operator()...;};
    template<typename... Ts> overloaded(Ts...)->overloaded<Ts...>;
    
    int main()
    {
      container c;
      for(int i=2;i--;){
        c.insert(variant(derived1()));
        c.insert(variant(derived2()));
      }
      
      for(const auto& x:c){
        boost::apply_visitor(
          overloaded{
            [](const derived1&){std::cout<<"derived1 ";},
            [](const derived2&){std::cout<<"derived2 ";}
          },
          x
        );
      }
    }
    

    Output

    derived2 derived2 derived1 derived1