Home C++ Generic way to provide `operator<<` for middleware generated types
Reply: 3

C++ Generic way to provide `operator<<` for middleware generated types

Slava
1#
Slava Published in 2018-01-10 19:42:58Z

We use a middleware that generates types for us for various programming languages, including C++. For structures generated for C++ I want to inject code that can be used for various data conversion, for example output to std::ostream. Let's say we have following structure generated:

struct Foo {
    int a;
    double d;
};

Let's say I change middleware compiler to produce following template function:

template<typename Visitor>
void visit( Visitor &v, const Foo &data )
{
    v.visit( "a", data.a );
    v.visit( "d", data.d );
}

now I can use this code for various ways and it should not affect anything if not used, for example make std::ostream::operator<<:

struct OstreamVisitor {
    OstreamVisitor( std::ostream &os ) : m_os( os ) {}

    void visit( const char *name, int i ) { m_os << name << "=" << i << std::endl; }
    void visit( const char *name, double d ) { m_os << name << "=" << d << std::endl; }

    std::ostream &m_os;
};

std::ostream &operator<<( std::ostream &out, const Foo &data )
{
    OstreamVisitor v( out );
    visit( v, data );
    return out;
}

it works live example but problem is I do not want to write std::ostream &operator<< for every structure manually so naive solution would be to write:

template<typename T>
std::ostream &operator<<( std::ostream &os, const T &t );

and use visitor, but that would interfere with other types, that not generated. So is there way to make such generic function that limited only for structures, that generated? I would like not to inject anything to generated structures if possible but having template class instead of template function is fine.

Note: I cannot restrict generated structs to belong to particular namespace unfortunately. What I am thinking is something like:

// system header
template<typename T>
struct visitable_tag;

// generated header
namespace FooNamespace {
struct Foo { ... };

template<>
struct visitable_tag<Foo> {};
}

 // or maybe have tag in special namespace
 namespace visitable_tag_namespace {
 template<>
 struct visitable_tag<FooNamespace::Foo> {};
 }

and then make template output function only instantiate when such specialization exits. Of course this is just idea of possible tagging and I open to other ways, but would prefer to have original structures untouched.

StPiere
2#
StPiere Reply to 2018-01-10 22:42:51Z

You could use combination of Macro's and SFINAE. Or take a look at Boost Hana:

http://boostorg.github.io/hana/index.html#tutorial-introspection-adapting

You can keep the macro stuff low level and buil your higher level code upon that

StPiere
3#
StPiere Reply to 2018-01-10 23:01:42Z

another approach could be using clang lib to autogenerate the output code from your structs. in that case your main code would stay clean. i'm not expert on this, but it may be worth a try

Slava
4#
Slava Reply to 2018-01-11 14:33:13Z

Here is what I implemented:

// header before generated types
namespace visitor_details {
template<typename T>
struct visitor_caller : std::false_type {};
}

// generated header
// types in various namespace
namespace foobar {

struct Foo { // this struct has generated helper
    int a = 1;
    double d = 2;
};

struct NotFoo { // this one does not, for testing
    int c;
};

}
// injected code generation
namespace visitor_details {
template<>
struct visitor_caller<foobar::Foo> : std::true_type {
    template<typename Visitor, typename T>
    static void visit( Visitor &v, T &&f )
    {
        v.visit( "a", f.a );
        v.visit( "d", f.d );
    }
};
}

// usage
struct OstreamVisitor {
    OstreamVisitor( std::ostream &os ) : m_os( os ) {}

    void visit( const char *name, int i ) { m_os << name << "=" << i << std::endl; }
    void visit( const char *name, double d ) { m_os << name << "=" << d << std::endl; }

    std::ostream &m_os;
};

template<typename T, typename EN = typename std::enable_if<visitor_details::visitor_caller<T>::value>::type >
std::ostream &operator<<( std::ostream &out, const T &data )
{
    OstreamVisitor v( out );
    visitor_details::visitor_caller<T>::visit( v, data );
    return out;
}

int main()
{
    foobar::Foo f;
    std::cout << f;
    foobar::NotFoo n;
    //std::cout << n;
}

now if I uncomment last line in main error says:

error: no match for ‘operator<<’ (operand types are ‘std::ostream {aka std::basic_ostream}’ and ‘foobar::NotFoo’)

so looks like this is what I need

live example

You need to login account before you can post.

About| Privacy statement| Terms of Service| Advertising| Contact us| Help| Sitemap|
Processed in 0.348871 second(s) , Gzip On .

© 2016 Powered by mzan.com design MATCHINFO