Last active
August 26, 2017 18:27
-
-
Save pthom/e40686d549af1e681f41670a93983755 to your computer and use it in GitHub Desktop.
Variations around named function parameters in C++ using lambdas
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This code explores some variations around the lambda proposition in the following article by Marco Arena | |
// https://marcoarena.wordpress.com/2014/12/16/bring-named-parameters-in-modern-cpp/ | |
#include <iostream> | |
#include <string> | |
//Version 1 (without macros) | |
// Will be called like this : | |
// | |
// foo_([](auto & p) { | |
// p.msg = "The answer is"; | |
// }, | |
// 42 // required params go here | |
// ); | |
struct foo_param { | |
int value; | |
std::string msg { "Default" }; | |
// value is a required param since it is a param of the constructor | |
// msg is an optional param | |
foo_param(int _v) { value = _v; } | |
}; | |
void foo(const foo_param& param) { | |
std::cout << param.msg << " " << param.value << std::endl; | |
} | |
template<typename paramModifierFunction, typename... Args> | |
auto foo_(paramModifierFunction fun, Args... args) { | |
foo_param p(args...); | |
fun(p); | |
return foo(p); | |
} | |
//Version 2 (with helper macro in order to create the adapted foo2_ version) | |
// Will be called like foo : | |
// | |
// foo2_([](auto & p) { | |
// p.msg = "The answer is"; | |
// }, | |
// 42 // required params go here | |
// ); | |
#define make_named_caller_detailed(function_name, param_name, dst_function_name) \ | |
template<typename ParamModifierFunction, typename... Args> \ | |
auto dst_function_name(ParamModifierFunction paramModifierFunction, Args... args) { \ | |
param_name param(args...); \ | |
paramModifierFunction(param); \ | |
return function_name(param); \ | |
} | |
#define make_named_caller(function_name) \ | |
make_named_caller_detailed(function_name, function_name##_param, function_name##_) | |
// How to use them | |
struct foo2_param { | |
std::string msg { "Default" }; | |
int value = 0; | |
foo2_param(int _v) { value = _v; } | |
}; | |
void foo2(const foo2_param & param) { | |
std::cout << param.msg << " " << param.value << std::endl; | |
} | |
make_named_caller(foo2) | |
// Version 3 : even more macro abuse, since we use a macro to hide the lambda creation. | |
// It will be called like this | |
// named_call( foo3, _.msg = "The answer is"; _.value= 42; ); | |
// It does *not* work with required params. | |
#define named_call(function_name, code) \ | |
function_name##_( [] (auto & _) { \ | |
code \ | |
}); | |
#define COMMA , | |
struct foo3_param { | |
std::string msg { "Default" }; | |
int value = 0; | |
}; | |
void foo3(const foo3_param & param) { | |
std::cout << param.msg << " " << param.value << std::endl; | |
} | |
make_named_caller(foo3) | |
// Version 4 : a version of named_call which is also compatible with required params | |
// it uses ##__VA_ARGS__ in order to remove the extra comma if needed. | |
// see https://stackoverflow.com/questions/5588855/standard-alternative-to-gccs-va-args-trick) | |
// Note : it would be better to have the required params before the optional ones : see named_call3 below | |
#define named_call2(function_name, code, ...)\ | |
function_name##_( [] (auto & _) { \ | |
code; \ | |
}, ##__VA_ARGS__); | |
struct foo6_param { | |
std::string msg { "Default" }; | |
std::string msg2 { "" }; | |
int value1 = 0; | |
int value2 = 0; | |
foo6_param(int _v1, int _v2) { value1 = _v1; value2 = _v2;} | |
}; | |
void foo6(const foo6_param & _) { | |
std::cout << _.msg << " " << _.msg2 << _.value1 + _.value2 << std::endl; | |
} | |
make_named_caller(foo6) | |
// Version 5 : another version of named_call which is also compatible with required params | |
// in which the required params come before the optional ones. The macros below are used to retrieve the last macro arg. | |
// This version is incompatible with visual studio ! | |
// | |
// Note : in order to make a version compatible with MSVC, some inspiration can be taken here : | |
// https://stackoverflow.com/questions/26682812/argument-counting-macro-with-zero-arguments-for-visualstudio-2010/26685339#26685339 | |
// count arguments | |
#define M_NARGS(...) M_NARGS_(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) | |
#define M_NARGS_(_10, _9, _8, _7, _6, _5, _4, _3, _2, _1, N, ...) N | |
#define M_NARGS_PLUS1(...) M_NARGS_PLUS1_(__VA_ARGS__, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) | |
#define M_NARGS_PLUS1_(_10, _9, _8, _7, _6, _5, _4, _3, _2, _1, N, ...) N | |
#define M_NARGS_MINUS1(...) M_NARGS_MINUS1_(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) | |
#define M_NARGS_MINUS1_(_10, _9, _8, _7, _6, _5, _4, _3, _2, _1, N, ...) N | |
#define M_NARGS_MINUS2(...) M_NARGS_MINUS2_(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1, 0) | |
#define M_NARGS_MINUS2_(_10, _9, _8, _7, _6, _5, _4, _3, _2, _1, N, ...) N | |
// utility (concatenation) | |
#define M_CONC(A, B) M_CONC_(A, B) | |
#define M_CONC_(A, B) A##B | |
#define M_GET_ELEM(N, ...) M_CONC(M_GET_ELEM_, N)(__VA_ARGS__) | |
#define M_GET_ELEM_0(_0, ...) _0 | |
#define M_GET_ELEM_1(_0, _1, ...) _1 | |
#define M_GET_ELEM_2(_0, _1, _2, ...) _2 | |
#define M_GET_ELEM_3(_0, _1, _2, _3, ...) _3 | |
#define M_GET_ELEM_4(_0, _1, _2, _3, _4, ...) _4 | |
#define M_GET_ELEM_5(_0, _1, _2, _3, _4, _5, ...) _5 | |
#define M_GET_ELEM_6(_0, _1, _2, _3, _4, _5, _6, ...) _6 | |
#define M_GET_ELEM_7(_0, _1, _2, _3, _4, _5, _6, _7, ...) _7 | |
#define M_GET_ELEM_8(_0, _1, _2, _3, _4, _5, _6, _7, _8, ...) _8 | |
#define M_GET_ELEM_9(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, ...) _9 | |
#define M_GET_ELEM_10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, ...) _10 | |
#define M_GET_UPTO_ELEM(N, ...) M_CONC(M_GET_UPTO_ELEM_, N)(__VA_ARGS__) | |
#define M_GET_UPTO_ELEM_0(_0, ...) | |
#define M_GET_UPTO_ELEM_1(_0, _1, ...) _0 | |
#define M_GET_UPTO_ELEM_2(_0, _1, _2, ...) _0, _1 | |
#define M_GET_UPTO_ELEM_3(_0, _1, _2, _3, ...) _0, _1, _2 | |
#define M_GET_UPTO_ELEM_4(_0, _1, _2, _3, _4, ...) _0, _1, _2, _3 | |
#define M_GET_UPTO_ELEM_5(_0, _1, _2, _3, _4, _5, ...) _0, _1, _2, _3, _4 | |
#define M_GET_UPTO_ELEM_6(_0, _1, _2, _3, _4, _5, _6, ...) _0, _1, _2, _3, _4, _5 | |
#define M_GET_UPTO_ELEM_7(_0, _1, _2, _3, _4, _5, _6, _7, ...) _0, _1, _2, _3, _4, _5, _6 | |
#define M_GET_UPTO_ELEM_8(_0, _1, _2, _3, _4, _5, _6, _7, _8, ...) _0, _1, _2, _3, _4, _5, _6, _7 | |
#define M_GET_UPTO_ELEM_9(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8 | |
#define M_GET_UPTO_ELEM_10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9 | |
// Get last argument - placeholder decrements by one | |
#define M_GET_LAST(...) M_GET_ELEM(M_NARGS(__VA_ARGS__), _, __VA_ARGS__ ,,,,,,,,,,,) | |
#define M_GET_ALL_BUT_LAST(...) M_GET_UPTO_ELEM(M_NARGS_MINUS1(__VA_ARGS__), __VA_ARGS__ ,,,,,,,,,,,) | |
#define named_call3(function_name, ...) \ | |
function_name##_( [] (auto & _) { \ | |
M_GET_LAST(__VA_ARGS__); \ | |
}, M_GET_ALL_BUT_LAST( __VA_ARGS__ )); | |
// Main | |
int main() { | |
// Version 1 ; standard lamdda | |
std::cout << "Version 1 : standard lambda" << std::endl; | |
foo_([](auto & p) { | |
p.msg = "The answer is"; | |
}, | |
42 // required params go here | |
); | |
// Version 2 ; standard lamdda, defined by macro | |
std::cout << "Version 2 : Standard lambda, defined by macro" << std::endl; | |
foo2_([](auto & p) { | |
p.msg = "The answer is"; | |
}, | |
42 // required params go here | |
); | |
// | |
// Version 3 : named_call | |
// | |
std::cout << "Version 3.1 : using named_call" << std::endl; | |
named_call( foo3, _.msg = "The answer is"; _.value= 42; ); | |
// More complex case, when you need to use comma | |
// in your code, replace it by another macro (sigh...) | |
std::cout << "Version 3.2 : using named_call and comma" << std::endl; | |
named_call( foo3, | |
_.msg = "The answer is"; | |
int a = 37 COMMA b = 5; | |
_.value= a + b; | |
); | |
// a trailing comma is required in order to use named_call with all | |
// default params values. | |
std::cout << "Version 3.3 : call with all default values" << std::endl; | |
named_call( foo3, ); | |
// However, this is equivalent to calling foo5(foo5_param()) | |
foo3( foo3_param() ); | |
// | |
// Version 4 : named_call2 | |
// named_call2 can also call a function with required params | |
// the required params come last, after the optional ones and they are separated by "," | |
// the optional params are separated by ";" | |
std::cout << "Version 4.1 : using named_call2" << std::endl; | |
named_call2( foo6, _.msg = "Ladies and gentlemen"; _.msg2 = "the answer is ", 35, 7 ); | |
// when using only required params, named_call2 will require a double comma (, ,) | |
std::cout << "Version 4.2 : using named_call2 with only required params" << std::endl; | |
named_call2( foo6, , 35, 7 ); | |
// However this line was equivalent to : | |
foo6(foo6_param(35, 7)); | |
// | |
// Version 5 : named_call3 | |
// named_call3 permits to specify the required parameters first. | |
// it will not work with msvc | |
std::cout << "Version 5.1 : using named_call3 with both required and optional params" << std::endl; | |
named_call3(foo6, 35, 7, _.msg = "Ladies and gentlemen,"; _.msg2= "the answer is "); | |
// when using only required params, named_call3 will require an ending | |
std::cout << "Version 5.2 : using named_call3 with only required params" << std::endl; | |
named_call3(foo6, 35, 7, ); | |
// however this is equivalent to | |
foo6(foo6_param(35, 7)); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment