Limits of BOOST_FUSION_ADAPT_STRUCT

6.1k views Asked by At

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?

2

There are 2 answers

0
NewbiZ On BEST ANSWER

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);
0
Laurent On

Thanks a lot Aurelien for your great detailed explanation. I've also found this post on google groups. As your explanation would lead to, the simplest way to get things working is put this in the demo namespace :

namespace demo{
    using boost::fusion::operators::operator<<;
    ...