How to Use Boost Preprocessor to Generate Accessors

How to use boost preprocessor to generate accessors?

Disclaimer:You should probably wait in case a better answer appears even if you are satisfied with this answer, because I'm far from an expert and these may not be the best approaches.

1st approach:

//two different sequences
struct A
{
MY_MACRO1((int)(float)(double),(x)(y)(z))
};

I think this approach gives the less scary-looking macro:

#define DECLARE_DATA_MEMBER1(R,TYPES,INDEX,NAME) \
BOOST_PP_SEQ_ELEM(INDEX,TYPES) BOOST_PP_CAT(m_,NAME);

#define DEFINE_ACCESSOR1(R,TYPES,INDEX,NAME) \
BOOST_PP_SEQ_ELEM(INDEX,TYPES) NAME(){ return BOOST_PP_CAT(m_,NAME); }

#define MY_MACRO1(TYPES,NAMES) \
BOOST_PP_SEQ_FOR_EACH_I(DECLARE_DATA_MEMBER1,TYPES,NAMES) \
public: \
BOOST_PP_SEQ_FOR_EACH_I(DEFINE_ACCESSOR1,TYPES,NAMES)

MY_MACRO gets two sequences: TYPES and NAMES. In order to declare the data members I use a BOOST_PP_SEQ_FOR_EACH_I on the sequence NAMES using the macro DECLARE_DATA_MEMBER1 and having the sequence TYPES as data. This "invokes" DECLARE_DATA_MEMBER1 with 4 parameters: R which is unused (and I have no idea what it does), TYPES (the sequence of types), INDEX (tells in which iteration we are right now, starting at 0), and NAME (the element of the original NAMES sequence that corresponds with this iteration).

The "bodies" of DECLARE_DATA_MEMBER1 and DEFINE_ACCESSOR1 are simple, we simply get the INDEXth element in the types sequence, and concatenate m_ with NAME.


2nd approach:

//just one sequence but you need to put two sets of parentheses around each pair
struct B
{
MY_MACRO2(((int, x))((float,y))((double,z)))
};

This one is still fairly simple, but has the inconvenient of having to use double parentheses.

#define DECLARE_DATA_MEMBER2(R,_,TYPE_AND_NAME) \
BOOST_PP_TUPLE_ELEM(2,0,TYPE_AND_NAME) BOOST_PP_CAT(m_,BOOST_PP_TUPLE_ELEM(2,1,TYPE_AND_NAME));

#define DEFINE_ACCESSOR2(R,_,TYPE_AND_NAME) \
BOOST_PP_TUPLE_ELEM(2,0,TYPE_AND_NAME) BOOST_PP_TUPLE_ELEM(2,1,TYPE_AND_NAME)(){ return BOOST_PP_CAT(m_,BOOST_PP_TUPLE_ELEM(2,1,TYPE_AND_NAME)); }

#define MY_MACRO2(TYPES_AND_NAMES) \
BOOST_PP_SEQ_FOR_EACH(DECLARE_DATA_MEMBER2,_,TYPES_AND_NAMES) \
public: \
BOOST_PP_SEQ_FOR_EACH(DEFINE_ACCESSOR2,_,TYPES_AND_NAMES)

This time there is only one sequence so we won't need the index in the helper macros. For this reason BOOST_PP_SEQ_FOR_EACH is used on TYPES_AND_NAMES using the macro DECLARE_DATA_MEMBER2 and without passing any extra data. This macro receives three "arguments": R again unused, _ (or DATA, also unused here), and TYPE_AND_NAME (a tuple in the form (TYPE,NAME)).

In the "bodies" of the two helper macros BOOST_PP_TUPLE_ELEM is used to get either the type(with index=0) or the name(with index=1). This macro needs to be passed the size of the tuple, the index of the element you want and the tuple.


3rd approach:

//one sequence but the macro is more complex
struct C
{
MY_MACRO3((int,x)(float,y)(double,z))
};

This macro borrows heavily from BOOST_FUSION_ADAPT_STRUCT and similar macros.

//Heavily "inspired" from BOOST_FUSION_ADAPT_STRUCT
#define CREATE_MY_MACRO_PLACEHOLDER_FILLER_0(X, Y) \
((X, Y)) CREATE_MY_MACRO_PLACEHOLDER_FILLER_1
#define CREATE_MY_MACRO_PLACEHOLDER_FILLER_1(X, Y) \
((X, Y)) CREATE_MY_MACRO_PLACEHOLDER_FILLER_0
#define CREATE_MY_MACRO_PLACEHOLDER_FILLER_0_END
#define CREATE_MY_MACRO_PLACEHOLDER_FILLER_1_END

#define DECLARE_DATA_MEMBER3(R,_,TYPE_AND_NAME) \
BOOST_PP_TUPLE_ELEM(2,0,TYPE_AND_NAME) BOOST_PP_CAT(m_,BOOST_PP_TUPLE_ELEM(2,1,TYPE_AND_NAME));

#define DEFINE_ACCESSOR3(R,_,TYPE_AND_NAME) \
BOOST_PP_TUPLE_ELEM(2,0,TYPE_AND_NAME) BOOST_PP_TUPLE_ELEM(2,1,TYPE_AND_NAME)(){ return BOOST_PP_CAT(m_,BOOST_PP_TUPLE_ELEM(2,1,TYPE_AND_NAME)); }

#define MY_MACRO3(TYPES_AND_NAMES) \
BOOST_PP_SEQ_FOR_EACH(DECLARE_DATA_MEMBER3,_,BOOST_PP_CAT(CREATE_MY_MACRO_PLACEHOLDER_FILLER_0 TYPES_AND_NAMES,_END)) \
public: \
BOOST_PP_SEQ_FOR_EACH(DEFINE_ACCESSOR3,_,BOOST_PP_CAT(CREATE_MY_MACRO_PLACEHOLDER_FILLER_0 TYPES_AND_NAMES,_END))

In this approach the helper macros are basically unchanged. The only (big) difference is that the sequence used in the for_each is not simply TYPES_AND_NAMES but BOOST_PP_CAT(CREATE_MY_MACRO_PLACEHOLDER_FILLER_0 TYPES_AND_NAMES,_END). This is a clever trick to force the double parentheses. It works like this:

CREATE_MY_MACRO_PLACEHOLDER_FILLER_0(int,x)(float,y)_END
//CREATE_MY_MACRO_PLACEHOLDER_FILLER_0(A,B)->((A,B))CREATE_MY_MACRO_PLACEHOLDER_FILLER_1
((int,x))CREATE_MY_MACRO_PLACEHOLDER_FILLER_1(float,y)_END
//CREATE_MY_MACRO_PLACEHOLDER_FILLER_1(A,B)->((A,B))CREATE_MY_MACRO_PLACEHOLDER_FILLER_0
((int,x))((float,y))CREATE_MY_MACRO_PLACEHOLDER_FILLER_0_END
//CREATE_MY_MACRO_PLACEHOLDER_FILLER_0_END->
((int,x))((float,y))

Running on Coliru.

Variadic macros: Reuse variadic arguments (Boost.Fusion)

It is relatively easy to construct a macro that generates all your boilerplate from an invocation of your preferred macro:

DEFINE_DATAVAR_STRUCT(
bar,
(double, bar1)
(float, bar2)
)

In this macro you have two arguments: a name (bar) and a "sequence" of tuples ((double,bar1)(float,bar2)). I put "sequence" in quotes because in order to have a sequence of tuples that can work with Boost.Preprocessor macros you need to have each tuple delimited by 2 sets of parentheses. In the example below most of the complexity is caused by the solution to this problem (there is more information about this in approach 3 in this answer.


Running on Wandbox

#include <iostream>
#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/tuple/elem.hpp>
#include <boost/preprocessor/cat.hpp>
#include <boost/fusion/include/define_assoc_struct.hpp>
#include <boost/fusion/include/at_key.hpp>

#define VAR(VARNAME) vardefs::VARNAME
#define VAR_PARENTHESES(VARNAME) (vardefs)(VARNAME)

//THIS IS ONLY NEEDED IN ORDER TO GET 2 SETS OF PARENTHESES
//Heavily "inspired" from BOOST_FUSION_ADAPT_STRUCT
#define GENERATE_DATAVAR_SEQUENCE_FILLER_0(X, Y) \
((X, Y)) GENERATE_DATAVAR_SEQUENCE_FILLER_1
#define GENERATE_DATAVAR_SEQUENCE_FILLER_1(X, Y) \
((X, Y)) GENERATE_DATAVAR_SEQUENCE_FILLER_0
#define GENERATE_DATAVAR_SEQUENCE_FILLER_0_END
#define GENERATE_DATAVAR_SEQUENCE_FILLER_1_END

#define GENERATE_DATAVAR_SEQUENCE(MEMBERS) BOOST_PP_CAT(GENERATE_DATAVAR_SEQUENCE_FILLER_0 MEMBERS,_END)

//THESE AREN'T ACTUALLY REQUIRED BUT HELP WITH READABILITY
#define DATAVAR_GET_TYPE(TUPLE) BOOST_PP_TUPLE_ELEM(2,0,TUPLE)
#define DATAVAR_GET_NAME(TUPLE) BOOST_PP_TUPLE_ELEM(2,1,TUPLE)

//THESE ARE THE HELPERS THAT ACTUALLY GENERATE THE VARIABLE PARTS OF THE MACRO
#define GENERATE_STRUCT_KEYS(_,__,TUPLE) struct DATAVAR_GET_NAME(TUPLE);
#define GENERATE_STRUCT_DEFINITION_SEQ(_,NAME,TUPLE) (DATAVAR_GET_TYPE(TUPLE),DATAVAR_GET_NAME(TUPLE),VAR(NAME)::keys::DATAVAR_GET_NAME(TUPLE))

// ===============================================================================================
#define DEFINE_DATAVAR_STRUCT(NAME,MEMBERS) \
namespace VAR(NAME)::keys{ \
BOOST_PP_SEQ_FOR_EACH(GENERATE_STRUCT_KEYS,_,GENERATE_DATAVAR_SEQUENCE(MEMBERS)) \
} \
namespace VAR(NAME) { struct index; } \
BOOST_FUSION_DEFINE_ASSOC_STRUCT( \
VAR_PARENTHESES(NAME), type, \
BOOST_PP_SEQ_FOR_EACH(GENERATE_STRUCT_DEFINITION_SEQ,NAME,GENERATE_DATAVAR_SEQUENCE(MEMBERS))\
)
// -----------------------------------------------------------------------------------------------

// ========================================================
DEFINE_DATAVAR_STRUCT(
foo,
(char, foo1)
(int, foo2)
(float, foo3)
)
// --------------------------------------------------------

// ========================================================
DEFINE_DATAVAR_STRUCT(
bar,
(double, bar1)
(float, bar2)
)
// --------------------------------------------------------

int main()
{
VAR(foo)::type fooI{'a',1,2.0f};
VAR(bar)::type barI{1.0,2.0f};

std::cout << boost::fusion::at_key<VAR(foo)::keys::foo1>(fooI) << std::endl;
std::cout << boost::fusion::at_key<VAR(foo)::keys::foo2>(fooI) << std::endl;
std::cout << boost::fusion::at_key<VAR(foo)::keys::foo3>(fooI) << std::endl;
std::cout << boost::fusion::at_key<VAR(bar)::keys::bar1>(barI) << std::endl;
std::cout << boost::fusion::at_key<VAR(bar)::keys::bar2>(barI) << std::endl;
}

Generate boilerplate code by transforming arguments to string literals

If you are interested in using the Boost.Preprocessor library you need to familiarize yourself with two fundamental "data types": sequence and tuple. You can find the whole list of macros that the library uses in the reference section of the documentation. I'll explain the ones I use below.

There are two macros in the interface: XML_TAG and DEFINE_XML_TAGS.

XML_TAG is really simple, it just puts its arguments inside two sets of parentheses. This causes that however many XML_TAGs you use will be converted to a sequence which elements are tuples (struct_name,sequence_of_type_and_name_pairs).

DEFINE_XML_TAGS is the macro that does all the work. It uses three helper macros GENERATE_STRUCT_DEFS, GENERATE_VARIANT_OF_TYPES and GENERATE_XMLTAGS.

GENERATE_VARIANT_OF_TYPES
Invokes ENUMERATE_TYPES(TAG_SEQ) in order to get a comma separated list of types.
Right now TAG_SEQ is ((Person,(int,age)))((Company,(int,noEmployees)(std::string,location))) and we want to have Person,Company.
BOOST_PP_ENUM(SEQ) takes a sequence and returns its elements separated by commas. So we need to have BOOST_PP_ENUM((Person)(Company)).
BOOST_PP_SEQ_FOR_EACH(MACRO,DATA,SEQ) calls MACRO with each of the elements in SEQ and whichever DATA you pass. So BOOST_PP_SEQ_FOR_EACH(GET_TYPE_SEQUENCE,_,TAG_SEQ) calls GET_TYPE_SEQUENCE with (Person,(int,age)) and (Company,(int,noEmployees)(sd:string,location)).
GET_TYPE_SEQUENCE then simply takes the first element of each tuple and puts it inside a set of parentheses using BOOST_PP_TUPLE_ELEM.

GENERATE_XML_TAGS
It calls GENERATE_PAIRS which in turn calls a SEQ_FOR_EACH using GENERATE_ONE_PAIR.
As explained in the previous section GENERATE_ONE_PAIR gets each of the tuples (struct_name, sequence_of_type_name_pairs). It takes the name and adds a pair of parentheses after it and then calls GENERATE_VECTOR_OF_MEMBER_NAMES with the sequence_of_type_name_pairs. GENERATE_VECTOR_OF_MEMBER_NAMES first adds the mandatory "name" member and then does something with BOOST_PP_ENUM very similar to the macro explained above with the difference that it needs to do a little trick because the current sequence of tuples does not have two sets of parentheses (this is explained here in the 3rd approach). GENERATE_MEMBER_NAME_SEQUENCE then simply takes the name of the member, converts it to string and then puts a set of parentheses around it.

GENERATE_STRUCT_DEFS
BOOST_PP_REPEAT(N,MACRO,DATA) calls MACRO N times, passing DATA and the current repetition index. GENERATE_ONE_STRUCT_DEF takes the index-th element of the sequence and then takes firstly the name of the struct and lastly the sequence of type-name pairs and calls DO_GENERATE_ONE_STRUCT_DEF with those values. Finally DO_GENERATE_ONE_STRUCT_DEF builds the BOOST_FUSION_DEFINE_STRUCT macro invocation.

I think, but I'm not knowledgeable enough to be sure, that there is a
bug in BOOST_FUSION_ADAPT_STRUCT_NAMESPACE_DEFINITION_END. It uses
BOOST_PP_REPEAT_1 directly when I think it should just use
BOOST_PP_REPEAT. I have undefined and redefined that macro using
BOOST_PP_REPEAT and everything seems to work, but you probably
shouldn't trust it blindly.

Test Running on WandBox

define_xml_tags.hpp

#include <boost/fusion/include/define_struct.hpp>
#include <boost/variant.hpp>
#include <vector>
#include <utility>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/preprocessor/seq/enum.hpp>
#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/seq/size.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/tuple/elem.hpp>

//I think there is a bug in the original macro, it uses BOOST_PP_REPEAT_1 where I think it should use BOOST_PP_REPEAT, but I don't know enough to know for sure
#undef BOOST_FUSION_ADAPT_STRUCT_NAMESPACE_DEFINITION_END

#define BOOST_FUSION_ADAPT_STRUCT_NAMESPACE_DEFINITION_END(NAMESPACE_SEQ) \
BOOST_PP_REPEAT( \
BOOST_PP_DEC(BOOST_PP_SEQ_SIZE(NAMESPACE_SEQ)), \
BOOST_FUSION_ADAPT_STRUCT_NAMESPACE_END_I, \
_)

//helps form a SEQUENCE of TUPLES
#define XML_TAG(NAME,MEMBER_SEQ) ((NAME,MEMBER_SEQ))

//helpers for GENERATE_STRUCT_DEFS, read from the bottom to the top
#define DO_GENERATE_ONE_STRUCT_DEF(NAME,MEMBER_SEQ) \
BOOST_FUSION_DEFINE_STRUCT( (), NAME, (std::string, name) MEMBER_SEQ)

#define GENERATE_ONE_STRUCT_DEF(Z,INDEX,TAG_SEQ) \
DO_GENERATE_ONE_STRUCT_DEF(BOOST_PP_TUPLE_ELEM(2,0,BOOST_PP_SEQ_ELEM(INDEX,TAG_SEQ)), BOOST_PP_TUPLE_ELEM(2,1,BOOST_PP_SEQ_ELEM(INDEX,TAG_SEQ)))

#define GENERATE_STRUCT_DEFS(TAG_SEQ) \
BOOST_PP_REPEAT(BOOST_PP_SEQ_SIZE(TAG_SEQ),GENERATE_ONE_STRUCT_DEF,TAG_SEQ)

//helpers for GENERATE_VARIANT_OF_TYPES, bottom to top
#define GET_TYPE_SEQUENCE(R,DATA,NAME_MEMBERSEQ_TUPLE) \
(BOOST_PP_TUPLE_ELEM(2,0,NAME_MEMBERSEQ_TUPLE))

#define ENUMERATE_TYPES(TAG_SEQ) \
BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_FOR_EACH(GET_TYPE_SEQUENCE,_,TAG_SEQ))

#define GENERATE_VARIANT_OF_TYPES(TAG_SEQ) \
typedef boost::variant<ENUMERATE_TYPES(TAG_SEQ)> Types;

//helpers for GENERATE_XMLTAGS, go from bottom to top in order to understand

//Heavily "inspired" from BOOST_FUSION_ADAPT_STRUCT
#define GENERATE_NAME_SEQUENCE_FILLER_0(X, Y) \
((X, Y)) GENERATE_NAME_SEQUENCE_FILLER_1
#define GENERATE_NAME_SEQUENCE_FILLER_1(X, Y) \
((X, Y)) GENERATE_NAME_SEQUENCE_FILLER_0
#define GENERATE_NAME_SEQUENCE_FILLER_0_END
#define GENERATE_NAME_SEQUENCE_FILLER_1_END

#define GENERATE_MEMBER_NAME_SEQUENCE(R,DATA,INDEX,TYPE_NAME_TUPLE) (BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(2,1,TYPE_NAME_TUPLE)))

#define GENERATE_VECTOR_OF_MEMBER_NAMES(MEMBER_SEQ) \
{ "name", BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_FOR_EACH_I(GENERATE_MEMBER_NAME_SEQUENCE,_,BOOST_PP_CAT(GENERATE_NAME_SEQUENCE_FILLER_0 MEMBER_SEQ,_END))) }

#define GENERATE_ONE_PAIR(R,DATA,NAME_MEMBERSEQ_TUPLE) \
{ BOOST_PP_TUPLE_ELEM(2,0,NAME_MEMBERSEQ_TUPLE)(), GENERATE_VECTOR_OF_MEMBER_NAMES(BOOST_PP_TUPLE_ELEM(2,1,NAME_MEMBERSEQ_TUPLE)) },

#define GENERATE_PAIRS(TAG_SEQ) \
BOOST_PP_SEQ_FOR_EACH(GENERATE_ONE_PAIR,_,TAG_SEQ)

#define GENERATE_XMLTAGS(TAG_SEQ) \
const std::vector<std::pair<Types,std::vector<std::string>>> xmlTags = { GENERATE_PAIRS(TAG_SEQ) };

//This is the actual macro, it simply invokes three different macros that do a different task each
#define DEFINE_XML_TAGS(TAG_SEQ) \
GENERATE_STRUCT_DEFS(TAG_SEQ) \
GENERATE_VARIANT_OF_TYPES(TAG_SEQ) \
GENERATE_XMLTAGS(TAG_SEQ)

main.cpp

#include <iostream>
#include <boost/fusion/include/io.hpp>
#include <boost/fusion/include/as_vector.hpp>
#include <boost/variant/static_visitor.hpp>

#include "define_xml_tags.hpp"

DEFINE_XML_TAGS(
XML_TAG(
Person,
(int, age)
)
XML_TAG(
Company,
(int, noEmployees)
(std::string, location)
)
)

struct printer : boost::static_visitor<void> {
void operator()(const Person& p) const
{
std::cout << "This is a person:" << boost::fusion::as_vector(p) << '\n';
}

void operator()(const Company& c) const
{
std::cout << "This is a company:" << boost::fusion::as_vector(c) << '\n';
}
};

void identify(Types v)
{
boost::apply_visitor(printer(),v);
}

int main()
{
Person p;
p.name="John";
p.age = 18;

identify(p);

Company c;
c.name="Mpany Co";
c.noEmployees=123;
c.location="Fake St";
identify(c);

std::cout << "\nChecking xmlTags:\n";
for(const auto& pair : xmlTags)
{
identify(pair.first);
std::cout << "It has the following members:\n";
for(const auto& str : pair.second)
std::cout << str << '\n';
}

std::cout << std::endl;
}

How to write read-only accessor functions in an aggregate root class?

Turning my comment into an answer.

If you decide to go with alternative 1 (N*K delegates), you can use Boost.Preprocessor to do the boilerplate work for you:

#include <boost/preprocessor.hpp>

// Define identifier names

#define FUNCTIONS (fun)(gun)(hun)

#define MEMBER_NAMES (m1_)(m2_)(m3_)

#define SUFFIXES (_1)(_2)(_3)

// Utility "data structure"
// Used to hand down state from iteration over functions to iteration over suffixes

#define WRAP_DATA(function, member) \
(2, (function, member))

#define UNWRAP_DATA_FUNTION(data) \
BOOST_PP_ARRAY_ELEM(0, data)

#define UNWRAP_DATA_MEMBER(data) \
BOOST_PP_ARRAY_ELEM(1, data)

// Accessor-generating functionality

// Convenience macro for generating the correct accessor name
#define CREATE_FUNCTION_NAME(data, suffix) \
BOOST_PP_CAT(UNWRAP_DATA_FUNCTION(data), suffix)

// Macro generating one accessor delegation
#define GENERATE_ACCESSOR(r, data, suffix) \
int CREATE_FUNCTION_NAME(data, suffix) () const { return UNWRAP_DATA_MEMBER(data).CREATE_FUNCTION_NAME(data, suffix) (); }

// Generate accessors

class C
{

// Execute GENERATE_ACCESSOR once for each element of SUFFIXES
#define BOOST_PP_LOCAL_MACRO(iter) \
BOOST_PP_SEQ_FOR_EACH(GENERATE_ACCESSOR, WRAP_DATA(BOOST_PP_SEQ_ELEM(iter, FUNCTIONS), BOOST_PP_SEQ_ELEM(iter, MEMBER_NAMES)), SUFFIXES)

#define BOOST_PP_LOCAL_LIMITS (0, BOOST_PP_SEQ_SIZE(FUNCTIONS) - 1)

// Execute BOOST_PP_LOCAL_MACRO once for each value within BOOST_PP_LOCAL_LIMITS
#include BOOST_PP_LOCAL_ITERATE()

// rest of class C here
// ...

};

Translated into pseudo-code to better highlight the working logic:

FUNCTIONS = {fun, gun, hun};
MEMBER_NAMES = {m1_, m2_, m3_};
SUFFIXES = {_1, _2, _3};

struct Data {
auto function, member;
};

auto createFunctionName(data, suffix) {
return data.function + suffix;
}

auto generateAccessor(data, suffix) {
return "int " + createFunctionName(data, suffix) + "() const { return " + data.member + "." + createFunctionName(data, suffix) + "(); }";
}

class C
{

for (i = 0; i < sizeof(FUNCTIONS); ++i) {
foreach (suffix in SUFFIXES) {
generateAccessor(Data(FUNCTIONS[i], MEMBER_NAMES[i]), suffix);
}
}

};

Use C++ macro string concatenation to access object properties

Two fixes (Verified by generating preprocessor output)

  1. Remove ## from cbs.##name
  2. cbs.##name(value) Callback doesn't take value as parameter.
#define ACCESSOR_WITH_CALLBACKS(name, location) \
auto& name() { return location; } \
auto name() const { return location; } \
void name(int value, const Callbacks& cbs) { \
name() = value; \
if (cbs.name != nullptr) { \
cbs.name(); \
} \
}

struct State2 {
private:
std::array<int, 2> data_;

public:
ACCESSOR_WITH_CALLBACKS(x, data_[0]);
ACCESSOR_WITH_CALLBACKS(y, data_[1]);
};


Related Topics



Leave a reply



Submit