Search code examples
c++metaprogrammingboost-fusion

Limits of BOOST_FUSION_ADAPT_STRUCT


I have tried to play with the BOOST_FUSION_ADAPT_STRUCT macro and tried some naive things such as use Fusion to print any arbitrary structure.

Starting from this example code given in the documentation, I was unable to perform on my adapted structure some operations that are allowed with a fusion sequence.

#include <boost/fusion/adapted.hpp>
#include <boost/fusion/sequence/io/out.hpp>
#include <boost/fusion/sequence/intrinsic.hpp>
#include <boost/fusion/view.hpp>
#include <iostream>

namespace fuz = boost::fusion;

namespace demo
{
    struct employee
    {
        std::string name;
        int age;
    };
}

// demo::employee is now a Fusion sequence
BOOST_FUSION_ADAPT_STRUCT(
    demo::employee,
    (std::string, name)
    (int, age))

int main()
{
    // tried to initialize an employee like a fusion sequence
    // but it didnt work
    // demo::employee e("bob", 42);

    demo::employee e;
    e.name = "bob";
    e.age = 42;

    // Access struct members with fusion random access functions
    // ok
    std::cout << fuz::at_c<0>(e) << std::endl; 

    // tried to print the struct like any othe fusion sequence
    // didnt work
    // std::cout << e << std::endl;

    // I made it work by using a fusion view
    // is it the right way?
    std::cout << fuz::as_nview<0, 1>(e) << std::endl;
}

This leads me to the following questions :

  • Why the Fusion magik does not operate here?

  • Using a view is the correct way to print an adapted struct?

  • How far can an adapted struct be used as a Fusion sequence?


Solution

  • From the boost::fusion documentation:

    The I/O operators are overloaded in namespace boost::fusion

    Which means that if you want a implicit integration of these operator<<, you will need to inject the boost::fusion namespace in your current namespace (:: here), or use them explicitly.

    To sum it all up, adding:

    using namespace boost::fusion;
    

    Should work in your case. Or for an explicit use, you will have to write:

    boost::fusion::operator<<(std::cout, e) << std::endl;
    

    --- EDIT ---

    After reading boost::fusion's code a bit, it seem that you are confused because of the Koenig's lookup of boost::fusion::operators::operator<< which is selected in case your argument is a real boost::fusion::sequence.

    This is why you don't need to inject the boost::fusion namespace, nor explicitly call boost::fusion::operator<< for types defined in the boost::fusion namespace.

    Some explanations:

    I won't explain the whole concept of Koenig's lookup (also known as Argument Dependent Lookup - ADL) here since that is not the point, but basically, it states that in case you are using a variable whose type is inside a namespace, then the function lookup extends to the namespace of that parameter.

    In this particular case, including boost/fusion/sequence/io/out.hpp will define boost::fusion::operator::operator<< which will then be injected in the boost::fusion namespace.

    $ cat /usr/local/include/boost/fusion/sequence/io/out.hpp
    [...]
    namespace boost { namespace fusion
    {
        [...]
        namespace operators
        {
            template <typename Sequence>
            inline typename
                boost::enable_if<
                   fusion::traits::is_sequence<Sequence>
                  , std::ostream&
                >::type // this is just a SFINAE trick to ensure
                        // the function will only be selected for
                        // actual boost::fusion::sequence
            operator<<(std::ostream& os, Sequence const& seq)
            {
                return fusion::out(os, seq); // this will print out the sequence
            }
        }
        using operators::operator<<; // here the operator<< is injected
                                     // in boost::fusion
    }}
    

    This means that calls using operator<< with parameters whose types are in the boost::fusion namespace will find the proper overload.

    Calls using arguments whose type is not located in this namespace will fail to resolve the proper overload of operator<< (this is the case in your example).

    You can check that by defining your type in the boost::fusion namespace.

    namespace boost { namespace fusion {
    struct employee
    {
      std::string name;
      int age;
    };
    }}
    
    BOOST_FUSION_ADAPT_STRUCT(
        boost::fusion::employee,
        (std::string, name)
        (int, age))
    
    [...]
    boost::fusion::employee e;
    std::cout << e << std::endl; // ADL will work here
    

    Side note: If you want to debug these kind of name lookup issues, you should use gdb. That way you will always know which overload was chosen. In this case:

    $ cat fusion.cpp
    #include <iostream>
    #include <cstdlib>
    
    #include <boost/fusion/container/vector.hpp>
    #include <boost/fusion/sequence/io.hpp>
    
    int main(int, char**)
    {
      boost::fusion::vector<int, char> foo(42, '?');
      std::cout << foo << std::endl;
    
      return EXIT_SUCCESS;
    }
    
    $ gdb -q ./fusion
    Reading symbols for shared libraries ... done
    (gdb) b 10
    Breakpoint 1 at 0x1000012f7: file fusion.cpp, line 10.
    (gdb) r
    Starting program: /Users/avallee/Projects/tmp/fusion
    Reading symbols for shared libraries ++............................. done
    
    Breakpoint 1, main (unnamed_arg=0x7fff5fbffb60, unnamed_arg=0x7fff5fbffb60) at fusion.cpp:10
    10    std::cout << foo << std::endl;
    (gdb) s
    boost::fusion::operators::operator<< <boost::fusion::vector<int, char, boost::fusion::void_, boost::fusion::void_, boost::fusion::void_, boost::fusion::void_, boost::fusion::void_, boost::fusion::void_, boost::fusion::void_, boost::fusion::void_> > (os=@0x7fff762b5f10, seq=@0x7fff5fbffb18) at out.hpp:38
    38              return fusion::out(os, seq);