Let's say I have some unspecified type called variant, as well as two functions allowing to convert to/from this type, with the following signature:
struct converter
{
template<typename T>
static variant to(const T&);
template<typename T>
static T from(const variant&);
};
Now, what I'd like to do is create wrappers for arbitrary C++ functions as in the following example:
SomeObject f_unwrapped(const std::string& s, int* x)
{
//... do something with the inputs...
return SomeObject();
}
extern "C" variant f(variant s, variant x)
{
return converter::to<SomeObject>(f_unwrapped(converter::from<std::string>(s), converter::from<int*>(x)));
}
Ideally I'd want the wrapper to be a one-line declaration or macro that would take only the f_unwrapped function and the name f as inputs.
I've tried to wrap the function into a function object, then do the bureaucratic work using variadic templates. While this does work, I don't know how to make the resulting function extern "C".
What is the most idiomatic way of achieving this goal?
If we use the EVAL, helper, Conditional, and map macros from the first two code blocks here.
The map will need to be made more general for our needs.
#define MM1() MM_CALL1
#define MM_NEXT1(Macro,a,...) \
IS_DONE(a)( \
EAT \
, \
OBSTRUCT(COMMA)() OBSTRUCT(MM1)() \
) \
(Macro,a,__VA_ARGS__)
#define MM_CALL1(Macro,a,...) \
Macro(a) \
MM_NEXT1(Macro,__VA_ARGS__)
#define MacroMap1(Macro,...) MM_CALL1(Macro,__VA_ARGS__,DONE)
#define MM2() MM_CALL2
#define MM_NEXT2(Macro,a,...) \
IS_DONE(a)( \
EAT \
, \
OBSTRUCT(COMMA)() OBSTRUCT(MM2)() \
) \
(Macro,a,__VA_ARGS__)
#define MM_CALL2(Macro,a,b,...) \
Macro(a,b) \
MM_NEXT2(Macro,__VA_ARGS__)
#define MacroMap2(Macro,...) MM_CALL2(Macro,__VA_ARGS__,DONE)
We will also want the WithTypes and WithoutTypes from here.
We can define AMACRO to do the job you wanted.
#define AsVariant(param) variant param
#define ConvertFrom(type,param) converter::from<type>(param)
#define HEADDER(type,func,params) type func ##_unwrapped (WithTypes params)
#define WRAPPER(type,func,params) \
extern "C" variant func (OBSTRUCT(MacroMap1)(AsVariant,WithoutTypes params)) \
{ \
return converter::to< type >(func ## _unwrapped( \
MacroMap2(ConvertFrom,IDENT params) \
)); \
}
#define AMACRO(type,func,params) \
EVAL( \
HEADDER(type,func,params); \
WRAPPER(type,func,params) \
HEADDER(type,func,params) \
)
Which will turn this:
AMACRO(SomeObject,f,(const std::string&, s, int*, x))
{
// ... do something with the inputs ...
return SomeObject();
}
Into this (after formatting):
SomeObject f_unwrapped (const std::string& s , int* x );
extern "C" variant f (variant s , variant x )
{
return converter::to<SomeObject>(f_unwrapped(converter::from<const std::string&>(s),converter::from<int*>(x)));
}
SomeObject f_unwrapped (const std::string& s , int* x )
{
return SomeObject();
}
NOTE:
If the const needs removing from the parameter, a conditional, similar to the ISDONE, can be made and added to the ConvertFrom macro.
Related
I want to write the following code but it can't be compiled.
(core code piece)
void g();
#define MACRO(x) \
#if (x == 0) \
g(); \
#else \
h(); \
#endif
MACRO(0)
I think the reason may be that #if and #else must be placed in different lines but #define statement can only have one line. I don't know how can I fix the code. Can anyone help me? Thank you very much!
Update: This question occurs when I want to export a list of functions, each of which has one or more internal implementation. All the implementations may be of best perforamance depending on the parameters. See the code below.
void fun_1_algo_1(int x);
void fun_1_algo_2(int x);
void fun_2_algo_1(int x);
void fun_2_algo_2(int x);
// fun_1 and fun_2 both have two implementations.
// If x > 1, algo_1 is faster; otherwise algo_2 is faster
void fun_3_aogo_1(int x);
void fun_4_aogo_1(int x);
#define CHECK(x) .... //check if x is a legal number
#define Export(i) \
void fun_##i(int x) { \
CHECK(x); \
#if (i >= 1 && i <= 2) \
if (x > 1) fun_##i##_algo_1(x); \
else fun_##i##_algo_2(x); \
#else \
fun_##i##_algo_1(x); \
#endif \
}
Export(1)
Export(2)
Export(3)
Export(4)
In this example, I can't change #if to if because fun_3_algo_2(x) is undefined. The exported function will be used as a library.
No, you can't do that. You could approximate a solution using templates though.
// generic template to call h() for all values of x
template<uint32_t x>
struct func_chooser {
static inline void f()
{ h(); }
};
// specialise for the zero case (calls g())
template<>
struct func_chooser<0> {
static inline void f()
{ g(); }
};
// the macro just defers to the template
#define MACRO(x) \
func_chooser<x>::f();
Is there a reason you need the inner test to be a macro?
#define MACRO(x) ((x) == 0 ? g() : h())
will give you the behavior you want, and if you call it with a constant
MACRO(0)
the optimizer will constant fold and dead code eliminate it (so you'll end up with just a single call in the executable.) The only real drawback of this is that you can call it with a non-constant and it will compile to a test and two calls.
If you really need to do this in the preprocessor, you can use various token-pasting tricks to build macros that "evaluate" expressions by defining lots of macros covering every possible combination. BOOST_PP exists which may do a lot of this for you, or you can define a minimal set that meets your needs. In your case, something like:
#define IF1OR2ELSE_1(T, F) T
#define IF1OR2ELSE_2(T, F) T
#define IF1OR2ELSE_3(T, F) F
#define IF1OR2ELSE_4(T, F) F
#define IF1OR2ELSE_(I, T, F) IF1OR2ELSE_##I(T, F)
#define IF1OR2ELSE(I, T, F) IF1OR2ELSE_(I, T, F)
#define Export(i) \
void fun_##i(int x) { \
CHECK(x); \
IF1OR2ELSE(i, \
if (x > 1) fun_##i##_algo_1(x); \
else fun_##i##_algo_2(x); , \
fun_##i##_algo_1(x); \
) \
}
Export(1)
Export(2)
Export(3)
Export(4)
should do the trick. The basic idea is that the macro IF1OR2ELSE will expand to either its 2nd or 3rd argument depending on whether the first argument is 1 or 2 -- note that that is a token not a value, so something like 1U is not the same as 1.
You can generalize the above by building a bunch of helper macros that evaluate expressions (like BOOST_PP does)
#define IFELSE_true(T, F) T
#define IFELSE_false(T, F) F
#define IFELSE_(C, T, F) IFELSE_##C(T, F)
#define IFELSE(C, T, F) IFELSE_(C, T, F)
#define EQ_0_0 true
#define EQ_0_1 false
#define EQ_0_2 false
... many (100s?) of these for many different values
#define EQ_4_4 true
#define EQ_(A,B) EQ_##A##_##B
#define EQ(A,B) EQ(A, B)
#define OR_true_true true
#define OR_true_false true
#define OR_false_true true
#define OR_false_false false
#define OR_(A, B) OR_##A##_##B
#define OR(A, B) OR_(A, B)
now you can use
IFELSE(OR(EQ(i,1), EQ(i, 2)),
..code for i == 1 or i == 2 ,
..code for other cases
)
in your macros.
No, you cannot use #if (or any other preprocessor directive) inside a #define. Just use an ordinary if and let the compiler optimize out the unused code branch as needed.
#define MACRO(x) \
{ \
if (x == 0) \
g(); \
else \
h(); \
}
#endif
MACRO(0)
I would like to create a magical macro, or anything, that would generate a something like this:
MAGICAL_MACRO(return_type, method_name, ...)
should work like this:
MAGICAL_MACRO(void, Foo, int a, int b)
->
virtual void Foo(int a, int b)
{
_obj->Foo(a, b);
}
Is this possible? I am afraid it is not.
Two questions: Are you open to a slightly different syntax for the arguments of MAGIC_MACRO? And can you use the Boost.Preprocessor header-only library?
If both answers are "yes", I have a solution for you:
#define MAGICAL_MACRO(Type, Name, ...) \
virtual Type Name(MAGICAL_GENERATE_PARAMETERS(BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))) {\
_obj->Name(MAGICAL_GENERATE_ARGUMENTS(BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))); \
}
#define MAGICAL_GENERATE_PARAMETERS(Args) \
BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(MAGICAL_MAKE_PARAMETER, %%, Args))
#define MAGICAL_GENERATE_ARGUMENTS(Args) \
BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(MAGICAL_MAKE_ARGUMENT, %%, Args))
#define MAGICAL_MAKE_PARAMETER(s, Unused, Arg) \
BOOST_PP_TUPLE_ELEM(2, 0, Arg) BOOST_PP_TUPLE_ELEM(2, 1, Arg)
#define MAGICAL_MAKE_ARGUMENT(s, Unused, Arg) \
BOOST_PP_TUPLE_ELEM(2, 1, Arg)
Usage looks like this:
MAGICAL_MACRO(void, Foo, (int, a), (int, b))
[Live example]
The %% used in the macro definitions is just my way of indicating "this value is not used." You could use pretty much anything else there (unless it contains a comma).
The above solution will work as long as the types involved are not spelled with a comma. If they are, introduce a type alias for them (typedef or using). Note that it is possible to get around this within the preprocessor magic itself, but it complicates already ugly code.
If you don't mind changing the syntax for the macro arguments, you could use following trick which abuses declaration syntax:
#define MAGICAL_MACRO(return_type, method_name, ...) \
virtual return_type method_name(__VA_ARGS__)
{ \
_obj->method_name(__VA_ARGS__); \
}
MAGICAL_MACRO(void, foo, int(a), int(b))
That will expand to:
virtual void foo(int(a), int(b))
{
_obj->foo(int(a), int(b));
}
Where void func(int(a), int(b)) is completely equivalent to void func(int a, int b).
The extra casts (or constructor calls depending on argument types) are ugly, but both GCC and Clang (with -O0) seem to ignore them not only for primitive types/PODs, but also for non-POD classes even if their copy constructors have side effects:
#include <iostream>
struct A
{
int x;
A(int value) : x(value) {}
A(const A &o)
{
x = o.x;
std::cout << "copy";
}
};
void func(A a)
{
std::cout << a.x << '\n';
}
void func1(A a)
{
func(a);
}
void func2(A a)
{
func(A(a));
}
int main()
{
func1(1); // prints `copy1`
func2(2); // prints `copy2`
}
The code below is working for what you've asked for with up to 1024 arguments and without using additional stuff like boost. It defines an EVAL(...) and also a MAP(m, first, ...) macro to do recursion and to use for each iteration the macro m with the next parameter first.
It is mostly copied from C Pre-Processor Magic. It is also great explained there. You can also download these helper macros like EVAL(...) at this git repository, there are also a lot of explanation in the actual code. It is variadic so it takes the number of arguments you want.
But I changed the FIRST and the SECOND macro as it uses a Gnu extension like it is in the source I've copied it from.
To split arguments like int a into int and a I used this answer from SO.
Your macro will be:
#define MAGICAL_MACRO(return_type, method_name, ...) \
virtual return_type method_name(__VA_ARGS__) \
{ \
return _obj->method_name(EVAL(MAP(TYPE_NAME, __VA_ARGS__))); \
}
Examples and limitations:
MAGICAL_MACRO(void, FOO, int a, double b, char c);
--> virtual void FOO(int a, double b, char c) { return _obj->FOO(a , b , c); };
MAGICAL_MACRO(int, FOO, int a, double b, char c);
--> virtual int FOO(int a, double b, char c) { return _obj->FOO(a , b , c); } ;
MAGICAL_MACRO(void, FOO, int* a, double* b, char* c);
--> virtual void* FOO(int* a, double* b, char* c) { return _obj->FOO(* a , * b , * c); };
/* maybe not what you want: pointer are dereferenced */
All the other macros needed, note that type splitting need to be defined per macro here:
/* Define all types here */
#define SPLIT_int int COMMA
#define SPLIT_char char COMMA
#define SPLIT_float float COMMA
#define SPLIT_double double COMMA
#define FIRST_(a, ...) a
#define SECOND_(a, b, ...) b
#define FIRST(...) FIRST_(__VA_ARGS__,)
#define SECOND(...) SECOND_(__VA_ARGS__,)
#define EMPTY()
#define EVAL(...) EVAL1024(__VA_ARGS__)
#define EVAL1024(...) EVAL512(EVAL512(__VA_ARGS__))
#define EVAL512(...) EVAL256(EVAL256(__VA_ARGS__))
#define EVAL256(...) EVAL128(EVAL128(__VA_ARGS__))
#define EVAL128(...) EVAL64(EVAL64(__VA_ARGS__))
#define EVAL64(...) EVAL32(EVAL32(__VA_ARGS__))
#define EVAL32(...) EVAL16(EVAL16(__VA_ARGS__))
#define EVAL16(...) EVAL8(EVAL8(__VA_ARGS__))
#define EVAL8(...) EVAL4(EVAL4(__VA_ARGS__))
#define EVAL4(...) EVAL2(EVAL2(__VA_ARGS__))
#define EVAL2(...) EVAL1(EVAL1(__VA_ARGS__))
#define EVAL1(...) __VA_ARGS__
#define DEFER1(m) m EMPTY()
#define DEFER2(m) m EMPTY EMPTY()()
#define DEFER3(m) m EMPTY EMPTY EMPTY()()()
#define DEFER4(m) m EMPTY EMPTY EMPTY EMPTY()()()()
#define IS_PROBE(...) SECOND(__VA_ARGS__, 0)
#define PROBE() ~, 1
#define CAT(a,b) a ## b
#define NOT(x) IS_PROBE(CAT(_NOT_, x))
#define _NOT_0 PROBE()
#define BOOL(x) NOT(NOT(x))
#define IF_ELSE(condition) _IF_ELSE(BOOL(condition))
#define _IF_ELSE(condition) CAT(_IF_, condition)
#define _IF_1(...) __VA_ARGS__ _IF_1_ELSE
#define _IF_0(...) _IF_0_ELSE
#define _IF_1_ELSE(...)
#define _IF_0_ELSE(...) __VA_ARGS__
#define HAS_ARGS(...) BOOL(FIRST(_END_OF_ARGUMENTS_ __VA_ARGS__)())
#define _END_OF_ARGUMENTS_() 0
#define MAP(m, first, ...) \
m(first) \
IF_ELSE(HAS_ARGS(__VA_ARGS__))( \
COMMA DEFER2(_MAP)()(m, __VA_ARGS__) \
)( \
/* Do nothing, just terminate */ \
)
#define _MAP() MAP
#define COMMA ,
#define CALL(A,B) A B
#define SPLIT(D) EVAL1(CAT(SPLIT_, D))
#define TYPE_NAME(D) CALL(SECOND,(SPLIT(D)))
Is there a way to generate this kind of macro with some other macro?
E.g. a struct with 3 members or something...
#define CONST_STRUCT2(name, name1, type1, name2, type2) \
struct name \
{ \
name(const type1& p##name1, const type2& p##name2) \
: name1(p##name1), name2(p##name2) {} \
const type1 name1; const type2 name2; \
};
Yes, you can do it with Boost.Preprocessor.
#define CONST_STRUCT(name, members) \
struct name \
{ \
name(GENERATE_CTOR_PARAMS(members)) \
: GENERATE_CTOR_INITIALISERS(members) {} \
GENERATE_MEMBERS(members) \
}
#define PARAM_TYPE(param) \
BOOST_PP_TUPLE_ELEM(2, 0, param)
#define PARAM_NAME(param) \
BOOST_PP_TUPLE_ELEM(2, 1, param)
#define GENERATE_CTOR_PARAMS(members) \
BOOST_PP_ENUM(BOOST_PP_SEQ_SIZE(members), GENERATE_CTOR_PARAM, members)
#define GENERATE_CTOR_PARAM(z, idx, members) \
const PARAM_TYPE(BOOST_PP_SEQ_ELEM(idx, members)) & PARAM_NAME(BOOST_PP_SEQ_ELEM(idx, members))
#define GENERATE_CTOR_INITIALISERS(members) \
BOOST_PP_ENUM(BOOST_PP_SEQ_SIZE(members), GENERATE_CTOR_INITIALISER, members)
#define GENERATE_CTOR_INITIALISER(z, idx, members) \
PARAM_NAME(BOOST_PP_SEQ_ELEM(idx, members)) (PARAM_NAME(BOOST_PP_SEQ_ELEM(idx, members)))
#define GENERATE_MEMBERS(members) \
BOOST_PP_REPEAT(BOOST_PP_SEQ_SIZE(members), GENERATE_MEMBER, members)
#define GENERATE_MEMBER(z, idx, members) \
const PARAM_TYPE(BOOST_PP_SEQ_ELEM(idx, members)) PARAM_NAME(BOOST_PP_SEQ_ELEM(idx, members));
Example of use:
CONST_STRUCT(Three, ((type1, name1))((type2, name2))((type3, name3)));
Necessary header files omitted for clarity. Also, in a real application, choose better scoped names (such as CONST_STRUCT_GENERATE_...).
I want to create a define to parse function signature and using Boost Preprocessor create something like this:
MY_DEFINE std::string fun(int t, float b)
{
or at least:
MY_DEFINE(std::string)(fun)(int t, float b)
{
that would generate:
class fun_in
{
int t;
float b;
}
class fun_out
{
std::string value;
}
void my_fun_wrapper(int t, float b)
{
}
std::string fun(int t, float b)
{
my_fun_wrapper(t, b);
for each function with that define.
Is it possible to create such define wrapper for function of N incoming arguments and any return type via Boost Preprocessor?
Well, the preprocessor can't parse tokens without pre-telling it. So you will need to use a lot more parenthesis instead. Here's what it would look like:
DEFINE( (std::string)(fun)((int) a, (float) b) )
{
return "Hello World!";
}
Here's how to create the macro using boost(I assume you are familiar with its preprocessor library). First is to define some macros for handling parenthesis, because boost doesn't handle sequences with commas at all:
#define REM(...) __VA_ARGS__
#define EAT(...)
// Retrieve the type
#define TYPEOF(x) DETAIL_TYPEOF(DETAIL_TYPEOF_PROBE x,)
#define DETAIL_TYPEOF(...) DETAIL_TYPEOF_HEAD(__VA_ARGS__)
#define DETAIL_TYPEOF_HEAD(x, ...) REM x
#define DETAIL_TYPEOF_PROBE(...) (__VA_ARGS__),
// Strip off the type
#define STRIP(x) EAT x
// Show the type without parenthesis
#define PAIR(x) REM x
Next, you need to handle the args in three different ways. First, is to output them as member variable(like int a; float b;). Then as functions arguments(like (int a, float b)). Then finally as forward arguments to pass along to the other function(like (a, b)).
#define DETAIL_DEFINE_MEMBERS_EACH(r, data, x) PAIR(x);
#define DETAIL_DEFINE_ARGS_EACH(r, data, i, x) BOOST_PP_COMMA_IF(i) PAIR(x)
#define DETAIL_DEFINE_FORWARD_EACH(r, data, i, x) BOOST_PP_COMMA_IF(i) STRIP(x)
#define DETAIL_DEFINE_MEMBERS(args) BOOST_PP_SEQ_FOR_EACH(DETAIL_DEFINE_MEMBERS_EACH, data, BOOST_PP_VARIADIC_TO_SEQ args)
#define DETAIL_DEFINE_ARGS(args) BOOST_PP_SEQ_FOR_EACH_I(DETAIL_DEFINE_ARGS_EACH, data, BOOST_PP_VARIADIC_TO_SEQ args)
#define DETAIL_DEFINE_FORWARD(args) BOOST_PP_SEQ_FOR_EACH_I(DETAIL_DEFINE_FORWARD_EACH, data, BOOST_PP_VARIADIC_TO_SEQ args)
Next we create a DETAIL_DEFINE macro that take three parameters. The first is the name of the function, the arguments, and then the return value. This will produce the classes and functions, like you want:
#define DETAIL_DEFINE(name, args, ...) \
struct BOOST_PP_CAT(name, _in) \
{ \
DETAIL_DEFINE_MEMBERS(args) \
}; \
struct BOOST_PP_CAT(name, _out) \
{ \
__VA_ARGS__ value; \
}; \
__VA_ARGS__ BOOST_PP_CAT(name, _impl) DETAIL_DEFINE_ARGS(args) ; \
__VA_ARGS__ name DETAIL_DEFINE_ARGS(args) \
{ \
return BOOST_PP_CAT(name, _impl) DETAIL_DEFINE_FORWARD(args); \
} \
__VA_ARGS__ BOOST_PP_CAT(name, _impl) DETAIL_DEFINE_ARGS(args)
Finally, the DEFINE macro will parse out all the parenthesis and pass them to the DETAIL_DEFINE macro:
#define DEFINE(x) DETAIL_DEFINE(TYPEOF(STRIP(x)), (TYPEOF(STRIP(STRIP(x)))), TYPEOF(x))
So now when you write:
DEFINE( (std::string)(fun)((int) a, (float) b) )
{
return "Hello World!";
}
It should output:
struct fun_in
{
int a;
float b;
};
struct fun_out
{
std::string value;
};
std::string fun_impl(int a, float b);
std::string fun(int a, float b)
{
return fun_impl(a, b);
}
std::string fun_impl(int a, float b)
{
return "Hello World!";
}
Note that this won't work in MSVC, there are workarounds though. Also, you need to compile with the -DBOOST_PP_VARIADICS=1.
What was the purpose of the last comma in this macro ?
#define TYPEOF(x) DETAIL_TYPEOF(DETAIL_TYPEOF_PROBE x,)
I think it has an extra comma at the end , and can be coded as
#define TYPEOF(x) DETAIL_TYPEOF(DETAIL_TYPEOF_PROBE x)
checking how it will work without last comma in this macro
1.) first argument in DETAIL_DEFINE
STRIP( (std::string)(fun)((int) a, (float) b) ) --> (fun)((int) a, (float) b)
TYPEOF( (fun)((int)a, (float) b)) ) --> fun
2.) second argument in DETAIL_DEFINE
STRIP( STRIP( (std::string)(fun)((int)a, (float) b) ) ) --> ( ( int ) a , ( float ) b )
TYPEOF ( ( ( int ) a , ( float ) b ) ) --> ( int ) a , ( float ) b
3.) third argument in DETAIL_DEFINE
TYPEOF ( (std::string )( fun )(( int ) a , ( float ) b) ) --> std::string
result :
DETAIL_DEFINE ( fun , (( int ) a , ( float ) b), std::string )
BTW, for those with old boost without BOOST_PP_VARIADIC_TO_SEQ use
BOOST_PP_TUPLE_TO_SEQ like this :
#define BOOST_PP_VARIADIC_TO_SEQ(...) BOOST_PP_TUPLE_TO_SEQ(PP_NARG(__VA_ARGS__) , (__VA_ARGS__))
And PP_NARG you can find anywhere by googling , here it is :
//Original Author: Unknown, but well recognized recursive variadic macro
#define PP_NARG(...) PP_NARG_IMPL(__VA_ARGS__,PP_RSEQ_N())
#define PP_NARG_IMPL(...) PP_ARG_N(__VA_ARGS__)
#define PP_ARG_N( \
_1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
_11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
_21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
_31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
_41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
_51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
_61,_62,_63,N,...) N
#define PP_RSEQ_N() \
63,62,61,60, \
59,58,57,56,55,54,53,52,51,50, \
49,48,47,46,45,44,43,42,41,40, \
39,38,37,36,35,34,33,32,31,30, \
29,28,27,26,25,24,23,22,21,20, \
19,18,17,16,15,14,13,12,11,10, \
9,8,7,6,5,4,3,2,1,0
Is it possible to mix up the BOOST_AUTO_TEST_CASE and BOOST_AUTO_TEST_CASE_TEMPLATE macros with the BOOST_PARAM_TEST_CASE in any way? I'm even interested in really messy ways of making this happen.
Having to build all of your test cases by hand seems really tedious. But the BOOST_PARAM_TEST_CASE mechanism is pretty darn useful, but only works if you have a test init function, which in turn requires you to have be using manual test case construction.
Is there any documentation on how to hook into the automated system yourself so you can provide your own tests that auto-register themselves?
I'm using boost 1.46 right now.
I wrote my own support for this since there really didn't seem to be any good support. This requires the C++11 decltype feature and the ::std::remove_const and ::std::remove_reference library methods to work.
The macro definitions are a modified versions of the BOOST_FIXTURE_TEST_CASE and BOOST_AUTO_TEST_CASE macros.
You use this by declaring your function thus:
BOOST_AUTO_PARAM_TEST_CASE(name, begin, end)
{
BOOST_CHECK_LT(param, 5); // The function will have an argument named 'param'.
}
Here is the header that defines the BOOST_AUTO_PARAM_TEST_CASE macro:
#include <boost/test/unit_test_suite.hpp>
#include <boost/test/parameterized_test.hpp>
#include <type_traits>
#define BOOST_FIXTURE_PARAM_TEST_CASE( test_name, F, mbegin, mend ) \
struct test_name : public F { \
typedef ::std::remove_const< ::std::remove_reference< decltype(*(mbegin)) >::type>::type param_t; \
void test_method(const param_t &); \
}; \
\
void BOOST_AUTO_TC_INVOKER( test_name )(const test_name::param_t ¶m) \
{ \
test_name t; \
t.test_method(param); \
} \
\
BOOST_AUTO_TU_REGISTRAR( test_name )( \
boost::unit_test::make_test_case( \
&BOOST_AUTO_TC_INVOKER( test_name ), #test_name, \
(mbegin), (mend))); \
\
void test_name::test_method(const param_t ¶m) \
// *******
#define BOOST_AUTO_PARAM_TEST_CASE( test_name, mbegin, mend ) \
BOOST_FIXTURE_PARAM_TEST_CASE( test_name, \
BOOST_AUTO_TEST_CASE_FIXTURE, \
mbegin, mend)
The solution provided by #Omnifarious works works, but requires a C++11 compiler.
Adapting that solution for a C++03 compiler:
#include <boost/test/unit_test_suite.hpp>
#include <boost/test/parameterized_test.hpp>
#define BOOST_FIXTURE_PARAM_TEST_CASE( test_name, F, P, mbegin, mend ) \
struct test_name : public F \
{ \
typedef P param_t; \
void test_method(const param_t &); \
}; \
\
void BOOST_AUTO_TC_INVOKER( test_name )(const test_name::param_t ¶m) \
{ \
test_name t; \
t.test_method(param); \
} \
\
BOOST_AUTO_TU_REGISTRAR( test_name )( \
boost::unit_test::make_test_case( \
&BOOST_AUTO_TC_INVOKER( test_name ), #test_name, \
(mbegin), (mend))); \
\
void test_name::test_method(const param_t ¶m) \
// *******
#define BOOST_AUTO_PARAM_TEST_CASE( test_name, param_type, mbegin, mend ) \
BOOST_FIXTURE_PARAM_TEST_CASE( test_name, \
BOOST_AUTO_TEST_CASE_FIXTURE, \
param_type, \
mbegin, mend)
This solution is slightly different is usage. Since there is no declspec in C++03, the type of the parameter object cannot be automatically deduced. We must pass it in as a parameter to BOOST_AUTO_PARAM_TEST_CASE:
class FooTestParam
{
public:
std::string mS;
FooTestParam (int n)
{
std::stringstream ss;
ss << n;
mS = ss.str();
}
};
FooTestParam fooParams [] =
{
FooTestParam (42),
FooTestParam (314)
};
BOOST_AUTO_PARAM_TEST_CASE (TestFoo, FooTestParam, fooParams, fooParams + 2)
{
const std::string testVal = param.mS;
}
BOOST_AUTO_TEST_CASE (TestAddressField)
{
const uint32_t raw = 0x0100007f; // 127.0.0.1
const uint8_t expected[4] = {127, 0, 0, 1};
const Mdi::AddressField& field = *reinterpret_cast <const Mdi::AddressField*> (&raw);
for (size_t i = 0; i < 4; ++i)
BOOST_CHECK_EQUAL (field[i], expected[i]);
}
Starting with Boost version 1.59, this is being handled by data-driven test cases:
#define BOOST_TEST_MODULE MainTest
#include <boost/test/included/unit_test.hpp>
#include <boost/test/data/test_case.hpp>
#include <boost/array.hpp>
static const boost::array< int, 4 > DATA{ 1, 3, 4, 5 };
BOOST_DATA_TEST_CASE( Foo, DATA )
{
BOOST_TEST( sample % 2 );
}
This functionality requires C++11 support from compiler and library, and does not work inside a BOOST_AUTO_TEST_SUITE.
If you have to support both old and new versions of Boost in your source, and / or pre-C++11 compilers, check out And-y's answer.
You can easily mix manual and automated test unit registration. Implement your own init function (like in example 20 on this page) and inside init function you can perform registration for parameterized test cases. Boost.Test will merge them both into single test tree.
Since Boost 1.59 internal details of realization was changed and Omnifarious's solution doesn't compile.
Reason ot that is changing signature of boost::unit_test::make_test_case function: now it take 2 additional args: __FILE__, __LINE__
Fixed solution:
#if BOOST_VERSION > 105800
#define MY_BOOST_TEST_ADD_ARGS __FILE__, __LINE__,
#define MY_BOOST_TEST_DEFAULT_DEC_COLLECTOR ,boost::unit_test::decorator::collector::instance()
#else
#define MY_BOOST_TEST_ADD_ARGS
#define MY_BOOST_TEST_DEFAULT_DEC_COLLECTOR
#endif
#define BOOST_FIXTURE_PARAM_TEST_CASE( test_name, F, mbegin, mend ) \
struct test_name : public F { \
typedef ::std::remove_const< ::std::remove_reference< decltype(*(mbegin)) >::type>::type param_t; \
void test_method(const param_t &); \
}; \
\
void BOOST_AUTO_TC_INVOKER( test_name )(const test_name::param_t ¶m) \
{ \
test_name t; \
t.test_method(param); \
} \
\
BOOST_AUTO_TU_REGISTRAR( test_name )( \
boost::unit_test::make_test_case( \
&BOOST_AUTO_TC_INVOKER( test_name ), #test_name, \
MY_BOOST_TEST_ADD_ARGS \
(mbegin), (mend)) \
MY_BOOST_TEST_DEFAULT_DEC_COLLECTOR); \
\
void test_name::test_method(const param_t ¶m) \
#define BOOST_AUTO_PARAM_TEST_CASE( test_name, mbegin, mend ) \
BOOST_FIXTURE_PARAM_TEST_CASE( test_name, \
BOOST_AUTO_TEST_CASE_FIXTURE, \
mbegin, mend)
I took Omnifarious' header file and modified it such that the parameter is passed to the constructor of the test fixture rather than to the test method. This requires the test fixture's constructor declaration to take a single argument with the parameter's type. I found this to be super handy--much thanks for the initial question and answer!
#include <boost/test/unit_test_suite.hpp>
#include <boost/test/parameterized_test.hpp>
#include <type_traits>
#define BOOST_FIXTURE_PARAM_TEST_CASE( test_name, F, mbegin, mend ) \
struct test_name : public F { \
typedef ::std::remove_const< ::std::remove_reference< decltype(*(mbegin)) >::type>::type param_t; \
test_name(const param_t ¶m) : F(param) {} \
void test_method(void); \
}; \
\
void BOOST_AUTO_TC_INVOKER( test_name )(const test_name::param_t ¶m)\
{ \
test_name t(param); \
t.test_method(); \
} \
\
BOOST_AUTO_TU_REGISTRAR( test_name )( \
boost::unit_test::make_test_case( \
&BOOST_AUTO_TC_INVOKER( test_name ), #test_name, \
(mbegin), (mend))); \
\
void test_name::test_method(void) \
// *******
#define BOOST_AUTO_PARAM_TEST_CASE( test_name, mbegin, mend ) \
BOOST_FIXTURE_PARAM_TEST_CASE( test_name, \
BOOST_AUTO_TEST_CASE_FIXTURE, \
mbegin, mend)