Search code examples
c++boostboost-variantboost-multi-index

multi_index_container doesn't work with recursive variant because of ambiguous operator=


Imagine we want to model C struct with dynamic C++ types. I.e. we have a set of fields, each field has a name and a value. The value can be a simple primitive type (let's say just int for the sake of the example) or another structure, i.e. another set of fields.

It's quite straightforward:

template <typename RecursiveVariant>
struct Field
{
    std::string      name;
    RecursiveVariant value;
};

template <typename RecursiveVariant>
using StructFields = std::vector<Field<RecursiveVariant>>;

typedef typename boost::make_recursive_variant<
    int
  , StructFields<boost::recursive_variant_>
  >::type FieldValue;

int main(int argc, char* argv[])
{
    FieldValue fv = 2;
    StructFields<FieldValue> sf;
    Field<FieldValue> f{"name", 2};
    sf.push_back(f);
    fv = sf;

    return 0;
}

It works as expected. However, if we try to use multi_index_container instead of std::vector, it won't compile. If I change the definition of StructFields to this:

template <typename RecursiveVariant>
using StructFields = multi_index_container<
    Field<RecursiveVariant>
  , indexed_by<
        sequenced<>
      , ordered_unique<
            member<
                Field<RecursiveVariant>
              , std::string
              , &Field<RecursiveVariant>::name
              >
          >
      >
  >;

The compiler (MSVC from VS 15.6.3) will issue

binary '=': no operator found which takes a right-hand operand of type 'boost::multi_index::multi_index_container,boost::multi_index::multi_index_container, ...

complaining on line fv = sf. It complains on ambiguity between variant& operator=(const variant& rhs) and variant& operator=(variant&& rhs). I am not sure how these operator= are even involved. Is there any recipe to fix this?


Solution

  • Like @sehe, I suspect the problem has to do with Boost.MPL magic (in particular, recognition of so-called placeholder expressions) somehow failing, but don't know why.

    FWIW, replacing the using StructFields bit with a hard type definition seems to solve the issue:

    template <typename RecursiveVariant>
    struct StructFields: multi_index_container<
        Field<RecursiveVariant>
      , indexed_by<
            sequenced<>
          , ordered_unique<
                member<
                    Field<RecursiveVariant>
                  , std::string
                  , &Field<RecursiveVariant>::name
                  >
              >
          >
      >{};
    

    or, better yet, do the hard-type trick only on the indices:

    template <typename RecursiveVariant>
    struct StructFieldsIndices:indexed_by<
        sequenced<>
      , ordered_unique<
            member<
                Field<RecursiveVariant>
              , std::string
              , &Field<RecursiveVariant>::name
              >
          >
      >{};
    
    template <typename RecursiveVariant>
    using StructFields = multi_index_container<
        Field<RecursiveVariant>
      , StructFieldsIndices<RecursiveVariant>
      >;
    

    Postscript: OK, I think I know what's going on. As previously noted, no problem arises when using "simpler" indices such as sequenced or random_access, it is when throwing ordered_unique in that things fail. These three are index specifiers declared as follows:

    template <typename TagList=tag<> >
    struct sequenced;
    template <typename TagList=tag<> >
    struct random_access;
    template<typename Arg1,typename Arg2=mpl::na,typename Arg3=mpl::na>
    struct ordered_unique;
    

    The peculiarity with ordered_unique is its mpl::na defaults: when in the context of defining StructFields<boost::recursive_variant_>, Boost.MPL "sees" those mpl::nas through the ordered_unique layer and fails to recognize the whole type as a placeholder expression with one arg.

    Post-postscript: I now really think what's going on :-) and it isn't related to mpl::na (in fact, sequenced has concealed mpl::nas in its default tag<> = tag<mp::na,...,mpl::na> argument and raises no error). The problem has to do with the &Field<RecursiveVariant>::name arg in member and the inability of Boost.MPL to substitute &Field<FieldValue>::name for &Field<boost::recursive_variant_>::name when processing the placeholder expression (I guess because this is a non-type template parameter). So, you can restrict the hard-type trick to just the definition of memberas follows:

    template <typename RecursiveVariant>
    struct FieldName: member<
        Field<RecursiveVariant>
      , std::string
      , &Field<RecursiveVariant>::name
      >{};
    
    template <typename RecursiveVariant>
    using StructFields = multi_index_container<
        Field<RecursiveVariant>
      , indexed_by<
            sequenced<>
          , ordered_unique<FieldName<RecursiveVariant>>
          >
      >;