1

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?

facetus
  • 1,091
  • 6
  • 20

2 Answers2

2

I tried with a simpler index: Live On Coliru

That gives no problem.

However, with the ordered index the FieldValue variant has a nested types type of:

boost::mpl::l_item<
    mpl_::long_<2>, int,
    boost::mpl::l_item<
        mpl_::long_<1>,
        boost::multi_index::multi_index_container<
            Field<boost::variant<
                boost::detail::variant::recursive_flag<int>,
                boost::multi_index::multi_index_container<
                    Field<boost::recursive_variant_>,
                    boost::multi_index::indexed_by<
                        boost::multi_index::sequenced<boost::multi_index::tag<mpl_::na> >,
                        boost::multi_index::ordered_unique<boost::multi_index::member<
                            Field<boost::recursive_variant_>,
                            std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >,
                            &Field<boost::recursive_variant_>::name> > >,
                    std::allocator<Field<boost::recursive_variant_> > > > >,
            boost::multi_index::indexed_by<boost::multi_index::sequenced<>,
                                           boost::multi_index::ordered_unique<boost::multi_index::member<
                                               Field<boost::recursive_variant_>, std::__cxx11::basic_string<char>,
                                               &Field<boost::recursive_variant_>::name> > >,
            std::allocator<Field<boost::variant<
                boost::detail::variant::recursive_flag<int>,
                boost::multi_index::multi_index_container<
                    Field<boost::recursive_variant_>,
                    boost::multi_index::indexed_by<
                        boost::multi_index::sequenced<boost::multi_index::tag<mpl_::na> >,
                        boost::multi_index::ordered_unique<boost::multi_index::member<
                            Field<boost::recursive_variant_>,
                            std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >,
                            &Field<boost::recursive_variant_>::name> > >,
                    std::allocator<Field<boost::recursive_variant_> > > > > > >,
        boost::mpl::l_end> >

As you can see, this, erroneously contains recursive_variant_ as opposed to the correct concrete type.

I think this is due the fact that Field<RecursiveVariant> hides the recursive_variant_ placeholder¹ from proper processing. I'm not too sure about that.

However, the good news is, you don't absolutely need to use make_recursive_variant here.

Here's something that I could make to work after... way too long fiddling around with forward declarations and wrappers

Live On Coliru

#include <boost/variant.hpp>
#include <string>
#include <vector>

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/member.hpp>

namespace bmi = boost::multi_index;

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

struct Field {
    std::string name;

    struct Value : boost::variant<int, boost::recursive_wrapper<StructFields<Field> > > {
        using base = boost::variant<int, boost::recursive_wrapper<StructFields<Field> > >;
        using base::base;
        using base::operator=;
    };

    Value value;
};

using FieldValue = Field::Value;

int main()
{
    FieldValue  fv = 2;
    StructFields<Field> sf;
    sf.push_back(Field{"name", 2});

    fv = sf;
}

¹ (which is just an alias for the MPL argument placeholder)

sehe
  • 374,641
  • 47
  • 450
  • 633
2

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>>
      >
  >;
Joaquín M López Muñoz
  • 5,243
  • 1
  • 15
  • 20
  • Aaahh. My bad, when I printed the type lists in my answer, I explicitly removed any `, _mpl::na` arguments... That should have been a hint – sehe Mar 20 '18 at 20:39
  • Btw, "using StructFieldsIndices = indexed_by<" worked too. – facetus Mar 21 '18 at 03:22
  • 1
    @noxmetus you sure? An alias template shouldn't be enough to hide `mpl::na`s away. In fact, my tests show this to fail: http://coliru.stacked-crooked.com/a/1f6a2a63b9efca13 – Joaquín M López Muñoz Mar 21 '18 at 08:57