Variadic Template Dispatcher - c++

I would like to use variadic templates to help solve an issue using va-args. Basically, I want to call a singular function, pass into the function a "command" along with a variable list of arguments, then dispatch the arguments to another function.
I've implemented this using tried and true (but not type safe) va_list. Here's an attempt I made at doing this using variadic templates. The example doesn't compile below as you will quickly find out why...
#include <iostream>
using namespace std;
typedef enum cmd_t
{
CMD_ZERO,
CMD_ONE,
CMD_TWO,
} COMMANDS;
int cmd0(double a, double b, double c)
{
cout << "cmd0 " << a << ", " << b << ", " << c << endl;
return 0;
}
int cmd1(int a, int b, int c)
{
cout << "cmd1 " << a << ", " << b << ", " << c << endl;
return 1;
}
template<typename... Args>
int DispatchCommand(COMMANDS cmd, Args... args)
{
int stat = 0;
switch (cmd)
{
case CMD_ZERO:
cmd0(args...);
break;
case CMD_ONE:
cmd1(args...);
break;
default:
stat = -1;
break;
}
return stat;
}
int main()
{
int stat;
stat = DispatchCommand(CMD_ZERO, 1, 3.141, 4);
stat = DispatchCommand(CMD_ONE, 5, 6, 7);
stat = DispatchCommand(CMD_TWO, 5, 6, 7, 8, 9);
system("pause");
return 0;
}
Does anyone have an idea on how I can modify this function to use variadic templates correctly?

Write some code that, given a function pointer and a set of arguments, calls it with the longest prefix of those arguments that works.
template<class...>struct types{using type=types;};
template<class types0, size_t N, class types1=types<>>
struct split;
template<class t00, class...t0s, size_t N, class...t1s>
struct split<types<t00,t0s...>,N,types<t1s...>>:
split<types<t0s...>,N-1,types<t1s...,t00>>
{};
template<class...t0s, class...t1s>
struct split<types<t0s...>,0,types<t1s...>>
{
using right=types<t0s...>;
using left=types<t1s...>;
};
template<class>using void_t=void;
template<class Sig,class=void>
struct valid_call:std::false_type{};
template<class F, class...Args>
struct valid_call<F(Args...), void_t<
decltype( std::declval<F>()(std::declval<Args>()...) )
>>:std::true_type {};
template<class R, class types>
struct prefix_call;
template<class R, class...Args>
struct prefix_call<R, types<Args...>> {
template<class F, class... Extra>
std::enable_if_t< valid_call<F(Args...)>::value, R >
operator()(R default, F f, Args&&...args, Extra&&...) const
{
return std::forward<F>(f)(args...);
}
template<class F, class... Extra>
std::enable_if_t< !valid_call<F(Args...)>::value, R >
operator()(R default, F f, Args&&...args, Extra&&...) const
{
return prefix_call<R, typename split<types<Args...>, sizeof...(Args)-1>::left>{}(
std::forward<R>(default), std::forward<F>(f), std::forward<Args>(args)...
);
}
};
template<class R>
struct prefix_call<R, types<>> {
template<class F, class... Extra>
std::enable_if_t< valid_call<F()>::value, R >
operator()(R default, F f, Extra&&...) const
{
return std::forward<F>(f)();
}
template<class F, class... Extra>
std::enable_if_t< !valid_call<F()>::value, R >
operator()(R default, F f, Extra&&...) const
{
return std::forward<R>(default);
}
};
the above code may contain typos.
template<typename... Args>
int DispatchCommand(COMMANDS cmd, Args... args)
{
int stat = 0;
switch (cmd) {
case CMD_ZERO: {
stat = prefix_call<int, Args...>{}(-1, cmd0, std::forward<Args>(args)...);
} break;
case CMD_ONE: {
stat = prefix_call<int, Args...>{}(-1, cmd1, std::forward<Args>(args)...);
} break;
default: {
stat = -1;
} break;
}
return stat;
}
If cmd0 or cmd1 is overridden, you'll have to use the overload set technique.

You may use the following:
template <COMMANDS cmd> struct command
{
template <typename ... Args>
int operator() (Args&&...) const { return -1; }
};
template <> struct command<CMD_ZERO>
{
int operator()(double a, double b, double c) const
{
std::cout << "cmd0 " << a << ", " << b << ", " << c << std::endl;
return 0;
}
};
template <> struct command<CMD_ONE>
{
int operator()(int a, int b, int c) const
{
std::cout << "cmd1 " << a << ", " << b << ", " << c << std::endl;
return 1;
}
};
template <COMMANDS cmd, typename... Args> int DispatchCommand(Args&&... args)
{
return command<cmd>()(std::forward<Args>(args)...);
}
And then use it like:
DispatchCommand<CMD_ZERO>(1., 3.141, 4.);
DispatchCommand<CMD_ONE>(5, 6, 7);
DispatchCommand<CMD_TWO>(5, 6, 7, 8, 9);
Live example
But using directly the different functions seems simpler:
cmd0(1., 3.141, 4.);
cmd1(5, 6, 7);

Related

Slice parameter pack parameters

I would like to split a parameter pack in all the first and one last parameter, but C++ requires the parameter pack be the last in the function declaration, so this is not valid code.
template<typename... Ts, typename Last>
void func( Ts... args, Last last ) {
cout << "{ ";
( (cout << args << ", "), ... ) << last << " }\n";
}
Now I can get there with a bit less nicer code like this:
template<typename T0, typename T1, typename Last>
pair< tuple<T0, T1>, tuple<Last> > slice( T0 t0, T1 t1, Last last ) {
return { make_tuple( t0, t1 ), make_tuple( last ) };
}
template<typename T0, typename T1, typename T2, typename Last>
pair< tuple<T0, T1, T2>, tuple<Last> > slice( T0 t0, T1 t1, T2 t2, Last last ) {
return { make_tuple( t0, t1, t2 ), make_tuple( last ) };
}
template<typename... Ts>
void func( Ts... ts ) {
auto f = [](auto last, auto... args) {
cout << "{ ";
( (cout << args << ", "), ... ) << last << " }\n";
};
apply( f, tuple_cat( slice(ts...).second, slice(ts...).first ) );
}
int main() {
func( "Hello", 4, 5 );
func( "Hello", 4, 5.4, "Mars"s );
}
But how do I make slice() properly?
https://godbolt.org/z/qbbP1YM9T
you can convert it to tuple them process it.
void f(){}; // empty pack
template<typename ...Ts>
void f(Ts&&... ts) {
auto tuple = std::forward_as_tuple(std::forward<Ts>(ts)...);
constexpr auto size = sizeof...(Ts);
auto&& last = std::get<size-1>(tuple);
[&]<std::size_t ...I>(std::index_sequence<I...>){
((std::cout << std::get<I>(tuple) << " , "), ...);
}(std::make_index_sequence<size-1>());
std::cout << last << '\n';
}
but in this case it's simpler to change the order (as # HolyBlackCat said)
template<typename T, typename ...Ts>
void f(T&& t, Ts&&... ts) {
std::cout << t;
((std::cout << " , " << ts),...);
std::cout << '\n';
}
Here is a slice function.
template<typename ...Ts>
auto slice_last(Ts&&... args){
auto tuple = std::forward_as_tuple(std::forward<Ts>(args)...);
constexpr auto size = sizeof...(Ts);
auto without_last =
[&]<std::size_t ...I>(std::index_sequence<I...>){
return std::forward_as_tuple(std::get<I>(std::move(tuple))...);
}(std::make_index_sequence<size-1>());
using last_type = std::tuple_element_t<size-1,decltype(tuple)>;
return std::pair<decltype(without_last),last_type>(
std::move(without_last),
std::get<size-1>(std::move(tuple))
);
}
// use
template<typename... Ts>
void f(Ts&&... ts) {
auto [pack,last] = slice_last(std::forward<Ts>(ts)...);
cout << "{ ";
std::apply(
[](auto&&...ts){( (std::cout<<ts<<" ,"), ... );}
,pack
);
std::cout << last << " }\n";
}
Here's a relatively simple implementation of args_filter_apply(), which calls a lambda with a runtime-filtered argument pack. It's not yet optimal, but you might get the idea:
#include <iostream>
template<typename Filter, typename Funct>
auto args_filter_apply(Filter filter, Funct funct)
{
return funct();
}
template<typename Filter, typename Funct, typename Arg, typename... Args>
auto args_filter_apply(Filter&& filter, Funct&& funct, Arg& arg, Args&... args)
{
if (filter(arg)) {
return args_filter_apply(std::forward<Filter>(filter), [&](auto... args1) {
return std::forward<Funct>(funct)(arg, args1...);
}, args...);
}
return args_filter_apply(std::forward<Filter>(filter), std::forward<Funct>(funct), args...);
}
template<typename... Ts>
void func(Ts... args) {
std::cout << "{ ";
int i = 0;
args_filter_apply([&](auto arg) {
return ++i != sizeof...(args);
}, [](auto... myArgs) {
( (std::cout << myArgs << ", "), ... );
}, args...);
i = 0;
args_filter_apply([&](auto arg) {
return ++i == sizeof...(args);
}, [](auto... last) {
( (std::cout << last), ...);
std::cout << " }\n";
}, args...);
}
int main()
{
func("Hello", 4, 5);
func("Hello", 4, 5.4, "Mars");
}
It's easy to create any slice desired if you first pack your arguments in a sequence. Since the standard does not provide a dedicated facility, tuples are usually used for that purpose.
So this is the workflow:
forward your arguments as tuple
create an implementation where the "slice" (here all but the last element) is accessed utilizing an index sequence
Use the last element as you please
Such an implementation is shown below:
template <std::size_t... Is, class Tuple>
void func_impl(std::index_sequence<Is...>, Tuple&& t)
{
cout << "{ ";
( (cout << std::get<Is>(t) << ", "), ... )
<< std::get<std::tuple_size_v<Tuple> - 1>(t)
<< " }\n";
}
template<typename... Ts>
void func(Ts&&... args)
{
func_impl(
std::make_index_sequence<sizeof...(Ts) - 1>{},
std::forward_as_tuple(std::forward<Ts>(args)...));
}
Demo
another option is to use the good old recursion
template<typename T>
void f_impl(T&& last){
std::cout << last << " }\n";
}
template<typename T, typename... Ts>
requires (sizeof...(Ts)!=0)
void f_impl(T t, Ts&&... ts) {
std::cout << t << ", ";
f_impl(std::forward<Ts>(ts)...);
}
template<typename... Ts>
void f(Ts&&... ts) {
std::cout << "{ ";
f_impl(std::forward<Ts>(ts)...);
}

tuple foreach, index, find: return compile-time constant during runtime

Tuple foreach is relatively simple using either recursion or std::apply:
#include <cstring>
#include <iostream>
#include <tuple>
#include <utility>
template<typename F, typename T>
auto foreach_apply(F&& f, T &&t) {
return std::apply([&f](auto&&... elements) {
return (f(std::forward<decltype(elements)>(elements)) || ...);
}, std::forward<T>(t));
}
template <std::size_t I=0, typename F, typename... Ts>
void foreach_recurse(F&& f, std::tuple<Ts...> t) {
if constexpr (I == sizeof...(Ts)) {
return;
} else {
f(std::get<I>(t));
find<I+1>(f, t);
}
}
int main() {
//auto a = std::tuple(true, true, false, false, true);
auto a = std::tuple("one", "two", "three", "four", "five");
//auto b = foreach_recurse([](auto& element) -> bool {
auto b = foreach_apply([](auto& element) -> bool {
std::cout << element << std::endl;
if (!strcmp("three", element))
return true;
else
return false;
}, a);
std::cout << "Done" << std::endl;
std::cout << b << std::endl << std::endl;
}
Indexing is only slightly trickier:
template <std::size_t I=0, typename F, typename... Ts>
size_t index_recurse(F&& f, std::tuple<Ts...> t) {
if constexpr (I == sizeof...(Ts)) {
return -1;
} else {
auto e = std::get<I>(t);
if (f(e))
return I;
return index_recurse<I+1>(f, t);
}
}
template <std::size_t I=0, typename F, typename... Ts>
bool index_recurse_2(F&& f, std::tuple<Ts...> t, size_t* i) {
if constexpr (I == sizeof...(Ts)) {
return false;
} else {
auto e = std::get<I>(t);
if (f(e)) {
*i = I;
return true;
}
return index_recurse_2<I+1>(f, t, i);
}
}
template<typename F, typename T>
auto index_apply(F&& f, T &&t, size_t* ix) {
return std::apply([&f,ix] (auto&&... elements) {
return [&f,ix]<std::size_t... I>(std::index_sequence<I...>, auto&&... elements) {
auto fi = [&f,ix](auto i, auto&& element) {
auto r = f(std::forward<decltype(element)>(element));
if (r)
*ix = i;
return r;
};
return (fi(I, std::forward<decltype(elements)>(elements)) || ...);
}
( std::make_index_sequence<sizeof...(elements)>()
, std::forward<decltype(elements)>(elements)...
);
}, std::forward<T>(t));
}
int main() {
/*
auto a = std::tuple("one", "two", "three", "four", "five");
auto b = index_recurse([](auto& element) -> bool {
std::cout << element << std::endl;
if (!strcmp("three", element))
return true;
else
return false;
}, a);
std::cout << "Done" << std::endl;
std::cout << b << std::endl << std::endl;
*/
/*
auto a = std::tuple("one", "two", "three", "four", "five");
size_t b;
auto c = index_recurse_2([](auto& element) -> bool {
std::cout << element << std::endl;
if (!strcmp("three", element))
return true;
else
return false;
}, a, &b);
std::cout << "Done" << std::endl;
std::cout << b << std::endl << std::endl;
*/
/*
auto a = std::tuple("one", "two", "three", "four", "five");
size_t b;
auto c = index_apply([](auto& element) -> bool {
std::cout << element << std::endl;
if (!strcmp("three", element))
return true;
else
return false;
}, a, &b);
std::cout << "Done" << std::endl;
std::cout << b << std::endl << std::endl;
*/
}
Now, get the value rather than the index. The index_* functions show that this is possible. We take a compile-time value (tuple) and a set of compile-time functions (unrolled index functions), apply a runtime function that matches an input, and get a runtime value that depends on the compile-time values. This is what the find_* functions attempt to do.
Given a tuple T of length N, find_broken_1 unrolls to N functions of type (i:0-N, T) where each function either returns the i-th function, or returns from the next in the sequence. That means the return type of the i-th function must match all the previous return types. So this recursive approach can't work.
template <std::size_t I=0, typename F, typename... Ts>
auto* find_broken_1(F&& f, std::tuple<Ts...> t) {
if constexpr (I == sizeof...(Ts)) {
// What type? Can this be fixed?
return (const char**)&"<>";
//return (void*)nullptr;
//return ((decltype(std::get<I-1>)::type)*)nullptr;
} else {
auto& e = std::get<I>(t);
if (f(e))
return &e;
std::cout << e << std::endl;
return find_broken_1<I+1>(f, t);
}
}
Here index is not known at compile-time so std::get won't compile. It would be nice if C++ would template for every possible index so this would work during runtime, just like C++ already unrolls every possible index function.
template <typename F, typename T>
auto find_broken_2(F&& f, T&& t, bool* b) {
const size_t i = index_recurse(f, t);
if (i >= 0) {
*b = true;
return std::get<i>(t);
}
else {
*b = false;
//return nullptr;
}
}
We know we can generate a runtime value from a compile-time value. If the transform is reversible and the bounds are known, we should be able to lookup a compile-time value from a runtime value. How can we trick the compiler into doing so? Then integrate this into the index_* functions to avoid double iteration.
Moving something into a type forces it to become a compile-time something. Unfortunately this unrolls to a different return type for each generated function, and since the returns are chained, generates a compile error. Once again, the recursive approach fails.
I'm just trying a bunch of different ideas, none working:
template<bool value, typename ...>
struct bool_type : std::integral_constant<bool , value> {};
//std::integral_constant<int, I> run_to_compile(size_t i, std::tuple<Ts...> t) {
template <std::size_t I=0, typename... Ts>
auto run_to_compile(size_t i, std::tuple<Ts...> t) {
if constexpr (I == sizeof...(Ts)) {
return std::integral_constant<int, I-1>();
// replace with static_assert... nope
//static_assert(bool_type<false, Ts...>::value, "wrong");
//return I-1;
}
else {
//if (i == I) {
if (I > 2) {
//return;
return std::integral_constant<int, I>();
//return I;
}
return run_to_compile<I+1>(i, t);
}
}
template<int value, typename ...>
struct int_type : std::integral_constant<int , value> {};
template <int I=0, typename T>
constexpr auto run_to_compile_2(int i) {
return int_type<i, T>();
}
template <typename F, typename T>
auto find_broken_3(F&& f, T&& t, bool* b) {
const size_t ir = index_recurse(f, t);
// nope
//constexpr decltype(t) temp = {};
// nope, again (really?)
//auto ic = std::integral_constant<int, ir>();
//constexpr auto ic = run_to_compile(0, temp);
//const auto ic = run_to_compile(ir, t);
//const auto ic = r2c_2<const int>(ir);
run_to_compile_2<2, int>(2);
const auto ic = 2;
if (ir >= 0) {
*b = true;
return std::get<ic>(t);
}
else {
*b = false;
//return nullptr;
}
}
How can I fix this?
Return type cannot depend of runtime. So a possibility to unify the return type is std::variant:
template <typename F, typename... Ts>
auto find_in_tuple(F&& f, std::tuple<Ts...> t)
{
std::optional<std::variant<Ts...>> res;
std::apply([&](auto&&... args){
auto lambda = [&](auto&& arg){
if (!res && f(arg))
res = arg;
};
(lambda(args), ...);
}, t);
return res;
}
Demo

Too many if/else statements in converting user inputs to types in C++

I have a template class with 3 template arguments.
template <class T, class U, class Y>
class MyClass {};
I wanna get input from users by CLI arguments, something like ./cli float driver-x load
The first arg can be float or double
The second arg is a driver name: driver-x, driver-y, ...
The third argument is about the action type: load, unload, ...
If I want to create a new instance of MyClass based on user inputs, I have to define many if/else statements. Because a user inputs are string and I have to prepare a condition on them.
So, it will be something like this:
if (data_type == "float")
if (driver == "driver-x")
if (action == "load")
MyClass<float, DriverX, Load> t;
t......
As far as I know, it's impossible to store a type in a variable in C++.
So, is there any way exists to improve the if/else statements? Something like:
if (data_type == "float")
//
if (driver == "driver-x")
//
if (action == "load")
//
MyClass<......> t;
t.....;
Or any other way?
I'm looking for a way to improve these if/else statements.
Here's my take
template<typename T>
struct proxy { // or std::type_identity
using type = T;
};
template<typename... Ts>
using choice_of = std::variant<proxy<Ts>...>;
template<typename T, typename>
using type_const_t = T;
template<typename T, typename... Ts>
std::optional<choice_of<T, Ts...>> choose(std::string const &choice, std::string const &head, type_const_t<std::string const&, Ts>... tail) noexcept {
if(choice == head) return proxy<T>{};
else if constexpr(sizeof...(Ts) == 0) return std::nullopt;
else if(auto rec = choose<Ts...>(choice, tail...)) return std::visit(
[](auto rec) -> choice_of<T, Ts...> { return rec; },
*rec);
else return std::nullopt;
}
auto data_choice = choose<float, double>(data_type, "float", "double");
auto driver_choice = choose<DriverX, DriverY>(driver, "driver-x", "driver-y");
auto action_choice = choose<Load, Unload>(action, "load", "unload");
std::visit([](auto data_type_p, auto driver_p, auto action_p) {
auto t = MyClass<typename decltype(data_type_p)::type, typename decltype(driver_p)::type, typename decltype(action_p)::type>{};
// do stuff with t
}, data_choice.value(), driver_choice.value(), action_choice.value());
Complete example on Godbolt
You can build some machinery to do this for you, extracting it into a function call.
For example, here I build a tuple which contains strings and types, then I check a passed string against all of them:
#include <string_view>
#include <cstddef>
#include <tuple>
#include <utility>
#include <type_traits>
template<class T>
struct mapped_type {
const std::string_view key;
using type = T;
explicit constexpr operator bool() const noexcept {
return true;
}
};
namespace detail {
template<class K, class F, class M, std::size_t I>
constexpr void lookup_impl(const K& key, F&& f, M&& m, std::integral_constant<std::size_t, I>) {
using tuple_t = typename std::remove_cv<typename std::remove_reference<M>::type>::type;
if constexpr (I < std::tuple_size<tuple_t>::value) {
const auto& mapping = std::get<I>(m);
if (mapping.key == key) {
std::forward<F>(f)(mapping);
return;
}
lookup_impl(key, std::forward<F>(f), std::forward<M>(m), std::integral_constant<std::size_t, I + 1>{});
} else {
std::forward<F>(f)(std::false_type{});
}
}
}
// Calls `f` with the first value from `m` that matches the key
// or `std::false_type{}` if no key matches.
template<class K, class F, class M>
constexpr void lookup(const K& key, F&& f, M&& m) {
detail::lookup_impl(key, std::forward<F>(f), std::forward<M>(m), std::integral_constant<std::size_t, 0>{});
}
// This is our mapping for the first argument
inline constexpr auto data_type_map = std::make_tuple(
mapped_type<float>{ "float" },
mapped_type<double>{ "double" }
);
// Example usage
#include <iostream>
int main() {
const char* s = "float";
lookup(s, [](const auto& arg) {
if constexpr (!arg) {
std::cout << "Invalid type\n";
} else {
using type = typename std::remove_cv<typename std::remove_reference<decltype(arg)>::type>::type::type;
std::cout << "Got type: " << typeid(type).name() << '\n';
}
}, data_type_map);
}
And then you can call this recursively inside the lambda.
You could also create a version that takes a tuple of keys and a tuple of values to call one function with many arguments:
#include <string_view>
#include <tuple>
#include <utility>
#include <type_traits>
template<class T>
struct mapped_type {
const std::string_view key;
using type = T;
explicit constexpr operator bool() const noexcept {
return true;
}
};
namespace detail {
template<class K, class F, class M, std::size_t I>
constexpr void lookup_impl(F&& f, const K& key, M&& m, std::integral_constant<std::size_t, I>) {
using tuple_t = typename std::remove_cv<typename std::remove_reference<M>::type>::type;
if constexpr (I < std::tuple_size<tuple_t>::value) {
const auto& mapping = std::get<I>(m);
if (mapping.key == key) {
std::forward<F>(f)(mapping);
return;
}
lookup_impl(std::forward<F>(f), key, std::forward<M>(m), std::integral_constant<std::size_t, I + 1>{});
} else {
std::forward<F>(f)(std::false_type{});
}
}
template<class F, class K, class M, std::size_t I>
constexpr void multilookup_impl(F&& f, const K& keys, M&& mappings, std::integral_constant<std::size_t, I>) {
constexpr std::size_t size = std::tuple_size<typename std::remove_cv<typename std::remove_reference<K>::type>::type>::value;
if constexpr (I >= size) {
std::forward<F>(f)();
} else {
lookup_impl([&](const auto& current_lookup) {
multilookup_impl(
[&](const auto&... args) { std::forward<F>(f)(current_lookup, args...); },
keys, mappings, std::integral_constant<std::size_t, I + 1>{}
);
}, std::get<I>(keys), std::get<I>(mappings), std::integral_constant<std::size_t, 0>{});
}
}
}
template<class F, class K, class M>
constexpr void lookup(F&& f, const K& keys, M&& mappings) {
using map_tuple_t = typename std::remove_cv<typename std::remove_reference<M>::type>::type;
using key_tuple_t = typename std::remove_cv<typename std::remove_reference<K>::type>::type;
constexpr std::size_t size = std::tuple_size<key_tuple_t>::value;
static_assert(size == std::tuple_size<map_tuple_t>::value, "Wrong number of keys for given number of maps");
detail::multilookup_impl(std::forward<F>(f), keys, mappings, std::integral_constant<std::size_t, 0>{});
}
Which looks almost the same, but there's one more level of calls.
It would be used like this:
#include <iostream>
inline constexpr auto data_type_map = std::make_tuple(
mapped_type<float>{ "float" },
mapped_type<double>{ "double" }
);
inline constexpr auto driver_type_map = std::make_tuple(
mapped_type<DriverX>{ "driver-x" },
mapped_type<DriverY>{ "driver-y" }
);
inline constexpr auto action_type_map = std::make_tuple(
mapped_type<Load>{ "load" },
mapped_type<Unload>{ "unload" }
);
int main() {
const char* a = "float";
const char* b = "driver-x";
const char* c = "load";
lookup([](const auto& data, const auto& driver, const auto& action) {
if constexpr (!data) {
std::cout << "Could not parse data!\n";
} else if constexpr (!driver) {
std::cout << "Could not parse driver!\n";
} else if constexpr (!action) {
std::cout << "Could not parse action!\n";
} else {
using data_type = typename std::remove_cv<typename std::remove_reference<decltype(data)>::type>::type::type;
using driver_type = typename std::remove_cv<typename std::remove_reference<decltype(driver)>::type>::type::type;
using action_type = typename std::remove_cv<typename std::remove_reference<decltype(action)>::type>::type::type;
MyClass<data_type, driver_type, action_type> t;
std::cout << "Constructed a " << typeid(decltype(t)).name() << '\n';
}
},
std::array<const char*, 3>{ a, b, c },
std::forward_as_tuple(data_type_map, driver_type_map, action_type_map)
);
}
I think you are looking for something like X-macros:
#define YOUR_TABLE \
X(float, DriverX, "driver-x", Load) \
X(int, DriverY, "driver-y", action2) \
X(int, DriverY, "driver-y", action3)
#define X(data_type, driver, driverName, action) if((0 == strcmp(#data_type,argv[1])) \
&& (0 == strcmp(driverName,argv[2])) && (0 == strcmp(#action,argv[3])))\
{ \
MyClass<data_type, driver, action> t; \
t.... \
}
YOUR_TABLE
#undef X
Prepare your puke-bag, here is a far-from-elegant solution but
simple enough to be easily adapted.
The main drawback I see is that all the remaining of the application
that needs to work with the created instance must stand in a
lambda-closure (this solution does not return this instance).
Every possible argument is considered only once in a
dedicated function (not X times Y times Z if/else).
/**
g++ -std=c++17 -o prog_cpp prog_cpp.cpp \
-pedantic -Wall -Wextra -Wconversion -Wno-sign-conversion \
-g -O0 -UNDEBUG -fsanitize=address,undefined
**/
#include <iostream>
#include <string>
#include <stdexcept>
//----------------------------------------------------------------------------
struct DriverX { auto show() const { return "DriverX"; } };
struct DriverY { auto show() const { return "DriverY"; } };
struct Load { auto show() const { return "Load"; } };
struct Unload { auto show() const { return "UnLoad"; } };
template<typename RealType,
typename DriverType,
typename ActionType>
struct MyClass
{
RealType real{};
DriverType driver{};
ActionType action{};
auto show() const
{
return std::to_string(sizeof(real))+" bytes real, "+
driver.show()+", "+action.show();
}
};
//----------------------------------------------------------------------------
template<typename RealType,
typename DriverType,
typename DoEverythingFunction>
void
with_MyClass_3(const std::string &action,
DoEverythingFunction fnct)
{
if(action=="load")
{
return fnct(MyClass<RealType, DriverType, Load>{});
}
if(action=="unload")
{
return fnct(MyClass<RealType, DriverType, Unload>{});
}
throw std::runtime_error{"unexpected action: "+action};
}
template<typename RealType,
typename DoEverythingFunction>
void
with_MyClass_2(const std::string &driver,
const std::string &action,
DoEverythingFunction fnct)
{
if(driver=="driver-x")
{
return with_MyClass_3<RealType, DriverX>(action, fnct);
}
if(driver=="driver-y")
{
return with_MyClass_3<RealType, DriverY>(action, fnct);
}
throw std::runtime_error{"unexpected driver: "+driver};
}
template<typename DoEverythingFunction>
void
with_MyClass(const std::string &real,
const std::string &driver,
const std::string &action,
DoEverythingFunction fnct)
{
if(real=="float")
{
return with_MyClass_2<float>(driver, action, fnct);
}
if(real=="double")
{
return with_MyClass_2<double>(driver, action, fnct);
}
throw std::runtime_error{"unexpected real: "+real};
}
//----------------------------------------------------------------------------
int
main(int argc,
char **argv)
{
std::cout << "~~~~ hardcoded types ~~~~\n";
const MyClass<float, DriverX, Load> mc1;
std::cout << "mc1: " << mc1.show() << '\n';
const MyClass<double, DriverY, Unload> mc2;
std::cout << "mc2: " << mc2.show() << '\n';
std::cout << "\n~~~~ many types ~~~~\n";
for(const auto &real: {"float", "double", "int"})
{
for(const auto &driver: {"driver-x", "driver-y", "driver-z"})
{
for(const auto &action: {"load", "unload", "sleep"})
{
try
{
with_MyClass(real, driver, action,
[&](const auto &mc)
{
std::cout << "working with: " << mc.show() << '\n';
});
}
catch(const std::exception &e)
{
std::cerr << "!!! " << e.what() << " !!!\n";
}
}
}
}
if(argc>3)
{
std::cout << "\n~~~~ from command line ~~~~\n";
try
{
with_MyClass(argv[1], argv[2], argv[3],
[&](const auto &mc)
{
std::cout << "working with: " << mc.show() << '\n';
});
}
catch(const std::exception &e)
{
std::cerr << "!!! " << e.what() << " !!!\n";
}
}
return 0;
}

Build function parameters with variadic templates

I have this sample code, which do what I need for a 3-parameter function :
template<typename T>T GETPARAM(void) { return T(); }
template<>int GETPARAM(void) { return 123; }
template<>double GETPARAM(void) { return 1.2345; }
template<>const char *GETPARAM(void) { return "hello"; }
template<typename P1, typename P2, typename P3, typename RES> RES BuildArgs3(RES(*fn)(P1, P2, P3)) {
P1 p1 = GETPARAM<P1>();
P2 p2 = GETPARAM<P2>();
P3 p3 = GETPARAM<P3>();
return fn(p1, p2, p3);
}
int print3(int a, double b, const char *c)
{
Cout() << "Print3:" << a << ", " << b << ", " << c << "\n";
return 1;
}
main() {
BuildArgs3(print3);
}
(the GETPARAM templates are there just to show the call).
I tried to generalize it with a variadic template for functions with any number of arguments with no success. Is it possible ?
The template shall be useable for any T (*fn)(P1, P2, ...) with any return type and any number of parameters, building the parameters on the fly calling the GETPARAM<Pn>() for each of them.
It's needed to create a binding system for a scripting language, fetching parameters from a stack and calling a C++ function when done.
I tried to generalize it with a variadic template for functions with any number of arguments with no success. Is it possible ?
Yes; and it's simple
template <typename R, typename ... Args>
R BuildArgsN (R(*fn)(Args...))
{ return fn(GETPARAM<Args>()...); }
The following is a full working example
#include <iostream>
template<typename T>T GETPARAM(void) { return T(); }
template<>int GETPARAM(void) { return 123; }
template<>double GETPARAM(void) { return 1.2345; }
template<>const char *GETPARAM(void) { return "hello"; }
template <typename R, typename ... Args>
R BuildArgsN (R(*fn)(Args...))
{ return fn(GETPARAM<Args>()...); }
int print3 (int a, double b, char const * c)
{
std::cout << "Print3:" << a << ", " << b << ", " << c << "\n";
return 1;
}
int main ()
{
BuildArgsN(print3);
}
If the calls to GETPARAM should be ordered then you have to expand the variadic pack in a context that guarantees a particular order. One option is list initialization:
Every initializer clause is sequenced before any initializer clause
that follows it in the braced-init-list. This is in contrast with the
arguments of a function call expression, which are unsequenced.
Let us consider your given example: You can expand the argument pack yielding the GETPARAM calls inside the curly braces constructing a proxy object. The proxy object can be implicitly convertible to the return type of your function.
#include <iostream>
int pos = 0;// DEBUG: observe the order of `GETPARAM` calls
template<typename T>T GETPARAM();
template<>
int GETPARAM() { return 100 + pos++; }
template<>
double GETPARAM() { return 100.5 + pos++; }
template<>
const char* GETPARAM() { pos++; return "hello"; }
////////////////////////////////////////////////////////////////////////////////
template<class Ret>
struct ArgEvalOrderer {
Ret ret;
template<class... Args>
ArgEvalOrderer(
Ret(*f)(Args...),
Args... args
)
: ret{f(args...)}
{}
operator Ret() const { return ret; }
};
template<class Ret, class... Args>
Ret call_after_ordered_argfetch(Ret(*f)(Args...)) {
// evaluation order guaranteed by braced init list
return ArgEvalOrderer<Ret>{f, GETPARAM<Args>()...};
}
template<class Ret, class... Args>
Ret call_after_ordered_argfetch_buggy(Ret(*f)(Args...)) {
// BUGGY: NO GUARANTEE on evaluation order
return ArgEvalOrderer<Ret>(f, GETPARAM<Args>()...);
}
template<class Ret, class... Args>
Ret call_after_unordered_argfetch(Ret(*f)(Args...)) {
// BUGGY: NO GUARANTEE on evaluation order
return f(GETPARAM<Args>()...);
}
int print7(int a, int b, double c, int d, double e, const char* f, double g) {
std::cout << "print7: " << a
<< ", " << b
<< ", " << c
<< ", " << d
<< ", " << e
<< ", " << f
<< ", " << g
<< std::endl;
return 1;
}
int main() {
call_after_ordered_argfetch(print7);
call_after_ordered_argfetch_buggy(print7);
call_after_unordered_argfetch(print7);
return 0;
}
Note that the upper version is the only one which guarantees ordered evaluation. In fact, I observe (online demo) the following output:
g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
print7: 100, 101, 102.5, 103, 104.5, hello, 106.5
print7: 113, 112, 111.5, 110, 109.5, hello, 107.5
print7: 120, 119, 118.5, 117, 116.5, hello, 114.5
Here is a code for statically building any function with any number of arguments. It Is completely independent from any compiler as gcc and libraries as std or stl (excepting the printf's used, but you can remove them safely). But requires c++0x because of the variadic templates.
#include <stdio.h>
// SIZEOF Type Package
template<typename ... Tn>
struct SIZEOF
{ static const unsigned int Result = 0; };
template<typename T1, typename ... Tn>
struct SIZEOF<T1, Tn ...>
{ static const unsigned int Result = sizeof(T1) + SIZEOF<Tn ...>::Result ; };
template<int ...>
struct MetaSequenceOfIntegers { };
template<int AccumulatedSize, typename Tn, int... GeneratedSequence>
struct GeneratorOfIntegerSequence;
template<
int AccumulatedSize,
typename Grouper,
typename Head,
typename... Tail,
int... GeneratedSequence
>
struct GeneratorOfIntegerSequence<
AccumulatedSize, Grouper( Head, Tail... ), GeneratedSequence... >
{
typedef typename GeneratorOfIntegerSequence
<
AccumulatedSize + sizeof(Head),
Grouper( Tail... ),
GeneratedSequence...,
AccumulatedSize
>::type type;
};
template<int AccumulatedSize, typename Grouper, int... GeneratedSequence>
struct GeneratorOfIntegerSequence<AccumulatedSize, Grouper(), GeneratedSequence...>
{
typedef MetaSequenceOfIntegers<GeneratedSequence...> type;
};
template<typename Tn>
class Closure;
template<typename ReturnType, typename... Tn>
class Closure<ReturnType( Tn... )>
{
public:
typedef ReturnType(*Function)(Tn ...);
static const unsigned int PARAMETERS_COUNT = sizeof...( Tn );
static const unsigned int PARAMETERS_LENGTH = SIZEOF<Tn ...>::Result;
private:
Function _entry;
char* _parameters;
public:
Closure(Function _entry, Tn ... an): _entry(_entry)
{
printf( "Closure::Closure(_entry=%d, PARAMETERS_COUNT=%d,
PARAMETERS_LENGTH=%d, sizeof=%d) => %d\n",
&_entry, PARAMETERS_COUNT, PARAMETERS_LENGTH, sizeof(*this), this );
if(PARAMETERS_LENGTH) _parameters = new char[PARAMETERS_LENGTH];
pack_helper( _parameters, an ... );
}
~Closure() {
printf( "Closure::~Closure(this=%d, _entry=%d,
PARAMETERS_COUNT=%d, PARAMETERS_LENGTH=%d, sizeof=%d)\n",
this, &_entry, PARAMETERS_COUNT, PARAMETERS_LENGTH, sizeof(*this) );
if(PARAMETERS_LENGTH) delete _parameters;
}
ReturnType operator()() {
return _run( typename GeneratorOfIntegerSequence< 0, int(Tn...) >::type() );
}
private:
template<int ...Sequence>
ReturnType _run(MetaSequenceOfIntegers<Sequence...>)
{
printf( "Closure::_run(this=%d)\n", this );
return _entry( unpack_helper<Sequence, Tn>()... );
}
template<const int position, typename T>
T unpack_helper()
{
printf( "Closure::unpack_helper(Head=%d, address=%d(%d), position=%d)\n",
sizeof( T ), _parameters + position, _parameters, position );
return *reinterpret_cast<T *>( _parameters + position );
}
public:
template<typename Head, typename ... Tail>
static void pack_helper(char* pointer_address, Head head, Tail ... tail)
{
printf( "Closure::pack_helper(
Head=%d, address=%d)\n", sizeof( Head ), pointer_address );
*reinterpret_cast<Head *>(pointer_address) = head;
pack_helper(pointer_address + sizeof( Head ), tail ...);
}
static void pack_helper(char* pointer_address) {}
};
/**
* Create a closure which can have any return type.
*/
template<typename ReturnType, typename ... Tn>
Closure< ReturnType(Tn ...) > create_closure(
ReturnType(*_entry)( Tn ... ), Tn ... an )
{
auto closure = new Closure< ReturnType(Tn ...) >( _entry, an ... );
printf( "create_closure=%d\n", closure );
return *closure;
}
char test_function1(char arg1, int arg2, bool arg3) {
printf(" test_function1: %c, %d, %d\n", arg1, arg2, arg3);
}
char test_function2(const char* arg1, const char* arg2, char arg3) {
printf(" test_function2: %s, %s, %c\n", arg1, arg2, arg3);
}
char test_function3() {
printf(" test_function3\n");
}
void test_function4() {
printf(" test_function4\n");
}
void test_function5(const char* arg1) {
printf(" test_function5=%s\n", arg1);
}
template<typename ... Tn>
void test_closure(Tn ... an) {
auto closure = create_closure(an ...);
closure();
printf( "\n" );
}
// clang++ -Xclang -ast-print -fsyntax-only test.cpp > expanded.cpp
int main()
{
test_closure( &test_function1, 'a', 10, false );
test_closure( &test_function2, "test1", "test2", 'b' );
test_closure( &test_function3 );
test_closure( &test_function4 );
test_closure( &test_function5, "Testa 3" );
test_closure( &test_function5, "Testa 4" );
}
Running it you will see the tests results:
$ g++ -o test test_variadic_critical_section_dynamic.cpp && ./test
Closure::Closure(_entry=-13672,
PARAMETERS_COUNT=3, PARAMETERS_LENGTH=6, sizeof=16) => 164864
Closure::pack_helper(Head=1, address=164976)
Closure::pack_helper(Head=4, address=164977)
Closure::pack_helper(Head=1, address=164981)
create_closure=164864
Closure::_run(this=-13520)
Closure::unpack_helper(Head=1, address=164981(164976), position=5)
Closure::unpack_helper(Head=4, address=164977(164976), position=1)
Closure::unpack_helper(Head=1, address=164976(164976), position=0)
test_function1: a, 10, 0
Closure::~Closure(this=-13520, _entry=-13520,
PARAMETERS_COUNT=3, PARAMETERS_LENGTH=6, sizeof=16)
Closure::Closure(_entry=-13672,
PARAMETERS_COUNT=3, PARAMETERS_LENGTH=17, sizeof=16) => 164976
Closure::pack_helper(Head=8, address=165008)
Closure::pack_helper(Head=8, address=165016)
Closure::pack_helper(Head=1, address=165024)
create_closure=164976
Closure::_run(this=-13520)
Closure::unpack_helper(Head=1, address=165024(165008), position=16)
Closure::unpack_helper(Head=8, address=165016(165008), position=8)
Closure::unpack_helper(Head=8, address=165008(165008), position=0)
test_function2: test1, test2, b
Closure::~Closure(this=-13520, _entry=-13520,
PARAMETERS_COUNT=3, PARAMETERS_LENGTH=17, sizeof=16)
Closure::Closure(_entry=-13624,
PARAMETERS_COUNT=0, PARAMETERS_LENGTH=0, sizeof=16) => 165008
create_closure=165008
Closure::_run(this=-13520)
test_function3
Closure::~Closure(this=-13520, _entry=-13520,
PARAMETERS_COUNT=0, PARAMETERS_LENGTH=0, sizeof=16)
Closure::Closure(_entry=-13624,
PARAMETERS_COUNT=0, PARAMETERS_LENGTH=0, sizeof=16) => 165040
create_closure=165040
Closure::_run(this=-13520)
test_function4
Closure::~Closure(this=-13520, _entry=-13520,
PARAMETERS_COUNT=0, PARAMETERS_LENGTH=0, sizeof=16)
Closure::Closure(_entry=-13624,
PARAMETERS_COUNT=1, PARAMETERS_LENGTH=8, sizeof=16) => 165072
Closure::pack_helper(Head=8, address=609568)
create_closure=165072
Closure::_run(this=-13520)
Closure::unpack_helper(Head=8, address=609568(609568), position=0)
test_function5=Testa 3
Closure::~Closure(this=-13520, _entry=-13520,
PARAMETERS_COUNT=1, PARAMETERS_LENGTH=8, sizeof=16)
Closure::Closure(_entry=-13624,
PARAMETERS_COUNT=1, PARAMETERS_LENGTH=8, sizeof=16) => 609568
Closure::pack_helper(Head=8, address=609600)
create_closure=609568
Closure::_run(this=-13520)
Closure::unpack_helper(Head=8, address=609600(609600), position=0)
test_function5=Testa 4
Closure::~Closure(this=-13520, _entry=-13520,
PARAMETERS_COUNT=1, PARAMETERS_LENGTH=8, sizeof=16)
You can run it with clang++ to see the generated template code:
$ clang++ -Xclang -ast-print -fsyntax-only test.cpp > expanded.cpp
// ...
private:
template<> char _run<<0, 8, 16>>(MetaSequenceOfIntegers<0, 8, 16>)
{
return this->_entry(
this->unpack_helper<0, const char *>(),
this->unpack_helper<8, const char *>(),
this->unpack_helper<16, char>()
);
}
template<> const char *unpack_helper<0, const char *>()
{
return *reinterpret_cast<const char **>(this->_parameters + 0);
}
template<> const char *unpack_helper<8, const char *>() {
return *reinterpret_cast<const char **>(this->_parameters + 8);
}
template<> char unpack_helper<16, char>() {
return *reinterpret_cast<char *>(this->_parameters + 16);
}
// ...
References
How to reverse an integer parameter pack?
Can we see the template instantiated code by C++ compiler
Variadic templates, parameter pack and its discussed ambiguity in a parameter list
"unpacking" a tuple to call a matching function pointer

Variadic templates and variadic functions combination

I'm creating a logging function that on the one hand needs to be able to parse any number of parameters, but on the other hand will always be passed with __func__ and this(of the calling object).
If I use only variadic templates like this
template<typename... Args>
void
log_device_message_template(const class *object,
char const *func,
char const *const format,
Args const &... args)noexcept
{
log(format, to_str(object).c_str(),format, to_str(args).c_str()...);
}
I have to call log_device_message_template like this :
log_device_message_template(this,__func__,some format,some parameters)
so i've added the macro:
#define log_api_call(format, ...) \
log_device_message_template( \
this, __func__, "%s::%s(" format ")", ##__VA_ARGS__)
The thing is that i'm getting a seg fault, probably due to a bad formatting somewhere. adding __attribiute__(format) doesn't work due to the use of variadic template...
here is the error from the python test testing the logger:
lookup in file=***** [0]
28371: symbol=_ZN5***6logger27log_device_message_templateIINS_14*****E13****EEEvNS_21logger_component_eENS_17logger_level_eEPKcPKNS_9objectES7_DpRT_; lookup in file=**** [0]
28371: ****: error: symbol lookup error: undefined symbol: _ZN5***6logger27log_device_message_templateIINS_14****E13****EEEvNS_21logger_component_eENS_17logger_level_eEPKcPKNS_9objectES7_DpRT_ (fatal)
Here's another way to approach it which neatly avoids macros.
Note in this case I am emitting log records to stdout, but that can easily be altered. I am also using std::ostringstream to build the log message. It's not renowned for performance, so is a good candidate for a customisation point.
However, this should get you started if you like the approach:
#include <iostream>
#include <sstream>
#include <tuple>
#include <utility>
// define a constant index type
template<std::size_t N> using index_c = std::integral_constant<std::size_t, N>;
// emitting an item at index 0 has no prefix
template<class T>
void emit_item(std::ostream &os, index_c<0>, T const &t)
{
os << t;
}
// emitting an item at index N (!= 0) has a comma prefix
template<std::size_t N, class T>
void emit_item(std::ostream &os, index_c<N>, T const &t)
{
os << ", " << t;
}
// emit args 0 .. N-1
template<class Tuple, std::size_t...Is>
void emit_arglist(std::ostream &sink, Tuple &&tuple, std::index_sequence<Is...>)
{
using expand = int[];
void(expand{0,
(emit_item(sink, index_c<Is>(), std::get<Is>(tuple)), 0)...
});
};
// emitting a 'more' at index 0 has a prefix
template<class T>
void emit_more(std::ostream &os, index_c<0>, T const &t)
{
os << " : " << t;
}
// emitting a 'more' at index N (!= 0) has a space prefix
template<std::size_t N, class T>
void emit_more(std::ostream &os, index_c<N>, T const &t)
{
os << " " << t;
}
template<class Tuple, std::size_t...Is>
void emit_more(std::ostream &sink, Tuple &&tuple, std::index_sequence<Is...>)
{
using expand = int[];
void(expand{0,
(emit_more(sink, index_c<Is>(), std::get<Is>(tuple)), 0)...
});
};
template<typename... Args, typename ...MoreStuff>
std::string
make_log_string(const char *object,
char const *func,
std::tuple<Args const &...> const& args,
MoreStuff&&...morestuff) noexcept
{
std::ostringstream ss;
ss << object << "::" << func << '(';
emit_arglist(ss, args, std::make_index_sequence<sizeof...(Args)>());
ss << ')';
emit_more(ss, std::tie(morestuff...), std::make_index_sequence<sizeof...(MoreStuff)>());
return ss.str();
}
// syntactic sugar for indicating arguments
template<class...Arg>
decltype(auto) args(Arg const&...args)
{
return std::tie(args...);
}
int main()
{
int a = 0, b = 1, c = 2;
std::string sa = "xxx", sb = "yyy", sc = "zzz";
const char* Class = "foo";
const char* Func = "var";
std::cout << make_log_string(Class, Func, args(a, b, c)) << std::endl;
std::cout << make_log_string(Class, Func, args(sa, b, sc)) << std::endl;
std::cout << make_log_string(Class, Func, args(sa, b, sc), "more stuff") << std::endl;
std::cout << make_log_string(Class, Func, args(), "empty", "argument", "list") << std::endl;
}
expected output:
foo::var(0, 1, 2)
foo::var(xxx, 1, zzz)
foo::var(xxx, 1, zzz) : more stuff
foo::var() : empty argument list
And with a bit more boilerplate we can write this:
std::cout << make_log_string(method(Class, Func)(a, b, c)) << std::endl;
std::cout << make_log_string(method(Class, Func)(sa, b, sc)) << std::endl;
std::cout << make_log_string(method(Class, Func)(sa, b, sc), "more stuff") << std::endl;
std::cout << make_log_string(method(Class, Func)(), "empty", "argument", "list") << std::endl;
Here it is:
#include <iostream>
#include <sstream>
#include <tuple>
#include <utility>
template<std::size_t N> using index_c = std::integral_constant<std::size_t, N>;
template<class T>
void emit_item(std::ostream &os, index_c<0>, T const &t)
{
os << t;
}
template<std::size_t N, class T>
void emit_item(std::ostream &os, index_c<N>, T const &t)
{
os << ", " << t;
}
template<class Tuple, std::size_t...Is>
void emit_arglist(std::ostream &sink, Tuple &&tuple, std::index_sequence<Is...>)
{
using expand = int[];
void(expand{0,
(emit_item(sink, index_c<Is>(), std::get<Is>(tuple)), 0)...
});
};
template<class T>
void emit_more(std::ostream &os, index_c<0>, T const &t)
{
os << " : " << t;
}
template<std::size_t N, class T>
void emit_more(std::ostream &os, index_c<N>, T const &t)
{
os << " " << t;
}
template<class Tuple, std::size_t...Is>
void emit_more(std::ostream &sink, Tuple &&tuple, std::index_sequence<Is...>)
{
using expand = int[];
void(expand{0,
(emit_more(sink, index_c<Is>(), std::get<Is>(tuple)), 0)...
});
};
template<class...Args>
struct method_with_args;
struct method
{
constexpr method(const char* c, const char* f) : klass(c), func(f) {}
const char* klass;
const char* func;
template<class...Args>
auto operator()(Args const&...args) -> method_with_args<Args...>;
friend std::ostream& operator<<(std::ostream& os, const method& m)
{
return os << m.klass << "::" << m.func;
}
};
template<class...Args>
struct method_with_args
{
friend std::ostream& operator<<(std::ostream& os, method_with_args const& ma)
{
os << ma.m << '(';
emit_arglist(os, ma.args, std::make_index_sequence<sizeof...(Args)>());
return os << ')';
}
method m;
std::tuple<Args const&...> args;
};
template<class...Args>
auto method::operator()(Args const&...args) -> method_with_args<Args...>
{
return method_with_args<Args...>{*this, std::tie(args...)};
}
struct function
{
const char* name;
};
template<typename Method, typename ...MoreStuff>
std::string
make_log_string(Method m,
MoreStuff &&...morestuff) noexcept
{
std::ostringstream ss;
ss << m;
emit_more(ss, std::tie(morestuff...), std::make_index_sequence<sizeof...(MoreStuff)>());
return ss.str();
}
int main()
{
int a = 0, b = 1, c = 2;
std::string sa = "xxx", sb = "yyy", sc = "zzz";
const char *Class = "foo";
const char *Func = "var";
std::cout << make_log_string(method(Class, Func)(a, b, c)) << std::endl;
std::cout << make_log_string(method(Class, Func)(sa, b, sc)) << std::endl;
std::cout << make_log_string(method(Class, Func)(sa, b, sc), "more stuff") << std::endl;
std::cout << make_log_string(method(Class, Func)(), "empty", "argument", "list") << std::endl;
}