Skip to content

Instantly share code, notes, and snippets.

@pthom
Last active August 26, 2017 18:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pthom/e40686d549af1e681f41670a93983755 to your computer and use it in GitHub Desktop.
Save pthom/e40686d549af1e681f41670a93983755 to your computer and use it in GitHub Desktop.
Variations around named function parameters in C++ using lambdas
// 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