visiting - acfr/comma GitHub Wiki
Visiting is traversing members of an object and doing something to each of them.
In comma, visiting:
- is applied to a single object (as opposed to visiting several object in parallel)
- supports, although not imposes, key-value semantics where the key may be visitable, too
- is done depth-first
- caveat: is recursive and therefore may not scale for very deep structures, e.g. arbitrary trees
- caveat: is specialized and unwound in compile time, which may substantially increase compilation time and therefore it may be better to put visiting traits (see below) in a separate header and include it only where the traits are actually used
In comma/visiting/traits.h, a declaration of traits:
template < typename T > traits;
as well as traits specialization for some basic containers like std::vector or std::map.
In comma/visiting/apply.h the apply() functions, like:
template < typename V, typename T > void apply( V& visitor, T& visited ); template < typename V, typename T > void apply( V& visitor, const T& visited );
See doxygen documentation for more details.
Since the visiting library as such does not implement any concrete functionality, it is better to start with the tutorials for concrete types of visiting (comma-separated values, name-value, etc). Nevertheless:
E.g. in foo.h:
// the only routine part: define how we visit foo in some header // assume, hello.h contains traits definition for hello_type, same as below namespace comma { namespace visiting { template <> traits< foo > { // non-const visitor template < typename K, typename V > void visit( const K& k, foo& f, V& v ) { v.apply( "hello", f.hello ); v.apply( "world", f.world ); v.apply( "index", f.index ); } // const visitor template < typename K, typename V > void visit( const K& k, const foo& f, V& v ) { v.apply( "hello", f.hello ); v.apply( "world", f.world ); v.apply( "index", f.index ); } }; } }
Say, in main.cpp:
int main( int, char** ) { // load foo from xml std::ifstream ifs( "foo.xml" ); foo f = comma::from_xml< foo >( ifs ); // output to stdout as json comma::to_json( std::cout, f ); // output to stdout as comma-separated values comma::csv::ascii_output_stream< foo > ostream( std::cout ); ostream.write( f ); }
foo can be a legacy or third-party class, but once you specialize the visiting traits for foo, no change is required for any of the visitors under the hood of functions from_xml(), to_json(), or ostream.write() to handle an instance of foo.
For simplicity's sake, assume that we deal with classes whose leaf members are either fundamental types or strings. The visitor below prints name and value of each leaf of an object.
struct print_name_value { // traverse template < typename K, typename T > void apply( const K& name, const T& value ) { visiting::do_while< !boost::is_fundamental< T >::value && !boost::is_same< T, std::string >::value >::visit( name, value, *this ); } // handle non-leaf elements template < typename K, typename T > void apply_next( const K& name, const T& value ) { comma::visiting::visit( name, value, *this ); } // handle leaf elements template < typename K, typename T > void apply_final( const K& key, char ) { std::cout << key << ": " << value << std::endl; } }
Usage:
int main( int, char** ) { foo f; f.world = "theatre"; f.index = 20; // either: comma::visiting::apply( print_name_value(), f ); // or an equivalent form: comma::visiting::apply( print_name_value() ).to( f ); }