Build function parameters with variadic templates - c++

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

Related

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;
}

Eigen::Ref for concatenating matrices

If I want to concatenate two matrices A and B, I would do
using Eigen::MatrixXd;
const MatrixXd A(n, p);
const MatrixXd B(n, q);
MatrixXd X(n, p+q);
X << A, B;
Now if n, p, q are large, defining X in this way would mean creating copies of A and B. Is it possible to define X as an Eigen::Ref<MatrixXd> instead?
Thanks.
No, Ref is not designed for that. We/You would need to define a new expression for that, that could be called Cat. If you only need to concatenate two matrices horizontally, in Eigen 3.3, this can be implemented in less than a dozen of lines of code as a nullary expression, see some exemple there.
Edit: here is a self-contained example showing that one can mix matrices and expressions:
#include <iostream>
#include <Eigen/Core>
using namespace Eigen;
template<typename Arg1, typename Arg2>
struct horizcat_helper {
typedef Matrix<typename Arg1::Scalar,
Arg1::RowsAtCompileTime,
Arg1::ColsAtCompileTime==Dynamic || Arg2::ColsAtCompileTime==Dynamic
? Dynamic : Arg1::ColsAtCompileTime+Arg2::ColsAtCompileTime,
ColMajor,
Arg1::MaxRowsAtCompileTime,
Arg1::MaxColsAtCompileTime==Dynamic || Arg2::MaxColsAtCompileTime==Dynamic
? Dynamic : Arg1::MaxColsAtCompileTime+Arg2::MaxColsAtCompileTime> MatrixType;
};
template<typename Arg1, typename Arg2>
class horizcat_functor
{
const typename Arg1::Nested m_mat1;
const typename Arg2::Nested m_mat2;
public:
horizcat_functor(const Arg1& arg1, const Arg2& arg2)
: m_mat1(arg1), m_mat2(arg2)
{}
const typename Arg1::Scalar operator() (Index row, Index col) const {
if (col < m_mat1.cols())
return m_mat1(row,col);
return m_mat2(row, col - m_mat1.cols());
}
};
template <typename Arg1, typename Arg2>
CwiseNullaryOp<horizcat_functor<Arg1,Arg2>, typename horizcat_helper<Arg1,Arg2>::MatrixType>
horizcat(const Eigen::MatrixBase<Arg1>& arg1, const Eigen::MatrixBase<Arg2>& arg2)
{
typedef typename horizcat_helper<Arg1,Arg2>::MatrixType MatrixType;
return MatrixType::NullaryExpr(arg1.rows(), arg1.cols()+arg2.cols(),
horizcat_functor<Arg1,Arg2>(arg1.derived(),arg2.derived()));
}
int main()
{
MatrixXd mat(3, 3);
mat << 0, 1, 2, 3, 4, 5, 6, 7, 8;
auto example1 = horizcat(mat,2*mat);
std::cout << example1 << std::endl;
auto example2 = horizcat(VectorXd::Ones(3),mat);
std::cout << example2 << std::endl;
return 0;
}
I'll add the C++14 version of #ggaels horizcat as an answer. The implementation is a bit sloppy in that it does not consider the Eigen compile-time constants, but in return it's only a two-liner:
auto horizcat = [](auto expr1, auto expr2)
{
auto get = [expr1=std::move(expr1),expr2=std::move(expr2)](auto row, auto col)
{ return col<expr1.cols() ? expr1(row, col) : expr2(row, col - expr1.cols());};
return Eigen::Matrix<decltype(get(0,0)), Eigen::Dynamic, Eigen::Dynamic>::NullaryExpr(expr1.rows(), expr1.cols() + expr2.cols(), get);
};
int main()
{
Eigen::MatrixXd mat(3, 3);
mat << 0, 1, 2, 3, 4, 5, 6, 7, 8;
auto example1 = horizcat(mat,2*mat);
std::cout << example1 << std::endl;
auto example2 = horizcat(Eigen::MatrixXd::Identity(3,3), mat);
std::cout << example2 << std::endl;
return 0;
}
Note that the code is untested.
That should be appropriate for most applications. However, in case you're using compile-time matrix dimensions and require maximum performance, prefer ggaels answer. In all other cases, also prefer ggaels answer, because he is the developer of Eigen :-)
I expanded ggael's answer to Array types, vertical concatenation, and more than two arguments:
#include <iostream>
#include <Eigen/Core>
namespace EigenCustom
{
using namespace Eigen;
constexpr Index dynamicOrSum( const Index& a, const Index& b ){
return a == Dynamic || b == Dynamic ? Dynamic : a + b;
}
enum class Direction { horizontal, vertical };
template<Direction direction, typename Arg1, typename Arg2>
struct ConcatHelper {
static_assert( std::is_same_v<
typename Arg1::Scalar, typename Arg2::Scalar
> );
using Scalar = typename Arg1::Scalar;
using D = Direction;
static constexpr Index
RowsAtCompileTime { direction == D::horizontal ?
Arg1::RowsAtCompileTime :
dynamicOrSum( Arg1::RowsAtCompileTime, Arg2::RowsAtCompileTime )
},
ColsAtCompileTime { direction == D::horizontal ?
dynamicOrSum( Arg1::ColsAtCompileTime, Arg2::ColsAtCompileTime ) :
Arg1::ColsAtCompileTime
},
MaxRowsAtCompileTime { direction == D::horizontal ?
Arg1::MaxRowsAtCompileTime :
dynamicOrSum( Arg1::MaxRowsAtCompileTime, Arg2::MaxRowsAtCompileTime )
},
MaxColsAtCompileTime { direction == D::horizontal ?
dynamicOrSum( Arg1::MaxColsAtCompileTime, Arg2::MaxColsAtCompileTime ) :
Arg1::MaxColsAtCompileTime
};
static_assert(
(std::is_base_of_v<MatrixBase<Arg1>, Arg1> &&
std::is_base_of_v<MatrixBase<Arg2>, Arg2> ) ||
(std::is_base_of_v<ArrayBase<Arg1>, Arg1> &&
std::is_base_of_v<ArrayBase<Arg2>, Arg2> )
);
using DenseType = std::conditional_t<
std::is_base_of_v<MatrixBase<Arg1>, Arg1>,
Matrix<
Scalar, RowsAtCompileTime, ColsAtCompileTime,
ColMajor, MaxRowsAtCompileTime, MaxColsAtCompileTime
>,
Array<
Scalar, RowsAtCompileTime, ColsAtCompileTime,
ColMajor, MaxRowsAtCompileTime, MaxColsAtCompileTime
>
>;
};
template<Direction direction, typename Arg1, typename Arg2>
class ConcatFunctor
{
using Scalar = typename ConcatHelper<direction, Arg1, Arg2>::Scalar;
const typename Arg1::Nested m_mat1;
const typename Arg2::Nested m_mat2;
public:
ConcatFunctor(const Arg1& arg1, const Arg2& arg2)
: m_mat1(arg1), m_mat2(arg2)
{}
const Scalar operator() (Index row, Index col) const {
if constexpr (direction == Direction::horizontal){
if (col < m_mat1.cols())
return m_mat1(row,col);
return m_mat2(row, col - m_mat1.cols());
} else {
if (row < m_mat1.rows())
return m_mat1(row,col);
return m_mat2(row - m_mat1.rows(), col);
}
}
};
template<Direction direction, typename Arg1, typename Arg2>
using ConcatReturnType = CwiseNullaryOp<
ConcatFunctor<direction,Arg1,Arg2>,
typename ConcatHelper<direction,Arg1,Arg2>::DenseType
>;
template<Direction direction, typename Arg1, typename Arg2>
ConcatReturnType<direction, Arg1, Arg2>
concat(
const Eigen::DenseBase<Arg1>& arg1,
const Eigen::DenseBase<Arg2>& arg2
){
using DenseType = typename ConcatHelper<direction,Arg1,Arg2>::DenseType;
using D = Direction;
return DenseType::NullaryExpr(
direction == D::horizontal ? arg1.rows() : arg1.rows() + arg2.rows(),
direction == D::horizontal ? arg1.cols() + arg2.cols() : arg1.cols(),
ConcatFunctor<direction,Arg1,Arg2>( arg1.derived(), arg2.derived() )
);
}
template<Direction direction, typename Arg1, typename Arg2, typename ... Ts>
decltype(auto)
concat(
const Eigen::DenseBase<Arg1>& arg1,
const Eigen::DenseBase<Arg2>& arg2,
Ts&& ... rest
){
return concat<direction>(
concat<direction>(arg1, arg2),
std::forward<Ts>(rest) ...
);
}
template<typename Arg1, typename Arg2, typename ... Ts>
decltype(auto)
concat_horizontal(
const Eigen::DenseBase<Arg1>& arg1,
const Eigen::DenseBase<Arg2>& arg2,
Ts&& ... rest
){
return concat<Direction::horizontal>(
arg1, arg2, std::forward<Ts>(rest) ...
);
}
template<typename Arg1, typename Arg2, typename ... Ts>
decltype(auto)
concat_vertical(
const Eigen::DenseBase<Arg1>& arg1,
const Eigen::DenseBase<Arg2>& arg2,
Ts&& ... rest
){
return concat<Direction::vertical>(
arg1, arg2, std::forward<Ts>(rest) ...
);
}
} // namespace EigenCustom
int main()
{
using namespace Eigen;
using namespace EigenCustom;
MatrixXd mat(3, 3);
mat << 0, 1, 2, 3, 4, 5, 6, 7, 8;
auto example1 = concat_horizontal(mat,2*mat);
std::cout << "example1:\n" << example1 << '\n';
auto example2 = concat_horizontal(VectorXd::Ones(3),mat);
std::cout << "example2:\n" << example2 << '\n';
auto example3 = concat_vertical(mat,RowVectorXd::Zero(3));
std::cout << "example3:\n" << example3 << '\n';
ArrayXXi arr (2,2);
arr << 0, 1, 2, 3;
auto example4 = concat_vertical(arr,Array2i{4,5}.transpose());
std::cout << "example4:\n" << example4 << '\n';
/* concatenating more than two arguments */
auto example5 = concat_horizontal(mat, mat, mat);
std::cout << "example5:\n" << example5 << '\n';
using RowArray2i = Array<int, 1, 2>;
auto example6 = concat_vertical( arr, RowArray2i::Zero(), RowArray2i::Ones() );
std::cout << "example6:\n" << example6 << '\n';
return 0;
}

Variadic Template Dispatcher

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);

std::string is passing the std::is_fundamental check when it should not - template metaprogramming

I'm having a problem with an assignment of mine. The question for the assignment is as follows:
Write a function template named Interpolate that will make the below work. Each argument will be output when its corresponding % is encountered in the format string. All output should be ultimately done with the appropriate overloaded << operator. A \% sequence should output a percent sign.
SomeArbitraryClass obj;
int i = 1234;
double x = 3.14;
std::string str("foo");
std::cout << Interpolate(R"(i=%, x1=%, x2=%\%, str1=%, str2=%, obj=%)", i, x, 1.001, str, "hello", obj) << std::endl;
If there is a mismatch between the number of percent signs and the number of arguments to output, throw an exception of type cs540::WrongNumberOfArgs.
Now, I've started to write the code to make it work. However, I'm running into a problem using non-PODs. Here is what I have written so far:
#include <iostream>
#include <sstream>
#include <string>
#include <type_traits>
std::string Interpolate(std::string raw_string) {
std::size_t found = raw_string.find_first_of("%");
if(found != std::string::npos && raw_string[found-1] != '\\') {
std::cout << "Throw cs540::ArgsMismatchException" << std::endl;
}
return raw_string;
}
template <typename T, typename ...Args>
std::string Interpolate(std::string raw_string, T arg_head, Args... arg_tail) {
std::size_t found = raw_string.find_first_of("%");
while(found != 0 && raw_string[found-1] == '\\') {
found = raw_string.find_first_of("%", found + 1);
}
if(found == std::string::npos) {
std::cout << "Throw cs540::ArgsMismatchException." << std::endl;
}
// Checking the typeid of the arg_head, and converting it to a string, and concatenating the strings together.
else {
if(std::is_arithmetic<T>::value) {
raw_string = raw_string.substr(0, found) + std::to_string(arg_head) + raw_string.substr(found + 1, raw_string.size());
}
}
return Interpolate(raw_string, arg_tail...);
}
int main(void) {
int i = 24332;
float x = 432.321;
std::string str1("foo");
//Works
std::cout << Interpolate(R"(goo % goo % goo)", i, x) << std::endl;
// Does not work, even though I'm not actually doing anything with the string argument
std::cout << Interpolate(R"(goo %)", str1) << std::endl;
}
This is a run time check semantically. This means that the code in the {} is compiled, even if the expression is always false:
if(std::is_arithmetic<T>::value) {
raw_string = raw_string.substr(0, found) + std::to_string(arg_head) + raw_string.substr(found + 1, raw_string.size());
}
to fix this, you can do this:
template<typename T>
void do_arithmetic( std::string& raw_string, T&& t, std::true_type /* is_arthmetic */ ) {
raw_string = raw_string.substr(0, found) + std::to_string(std::forward<T>(t)) + raw_string.substr(found + 1, raw_string.size());
}
template<typename T>
void do_arithmetic( std::string& raw_string, T&& t, std::false_type /* is_arthmetic */ ) {
// do nothing
}
then put in your code:
do_arithmetic( raw_string, arg_head, std::is_arithmetic<T>() );
which does a compile-time branch. The type of std::is_arithmetic is either true_type or false_type depending on if T is arithmetic. This causes different overloads of do_arithmetic to be called.
In C++1y you can do this inline.
template<typename F, typename...Args>
void do_if(std::true_type, F&& f, Args&&... args){
std::forward<F>(f)( std::forward<Args>(args)... );
}
template<typename...Args>
void do_if(std::false_type, Args&&...){
}
template<bool b,typename...Args>
void do_if_not(std::integral_constant<bool,b>, Args&& args){
do_if( std::integral_constant<bool,!b>{}, std::forward<Args>(args)... );
}
template<typename C, typename F_true, typename F_false, typename...Args>
void branch( C c, F_true&&f1, F_false&& f0, Args&&... args ){
do_if(c, std::forward<F_true>(f1), std::forward<Args>(args)... );
do_if_not(c, std::forward<F_false>(f0), std::forward<Args>(args)... );
}
which is boilerplate. We can then do in our function:
do_if(std::is_arithmetic<T>{},
[&](auto&& arg_head){
raw_string = raw_string.substr(0, found) + std::to_string(arg_head) + raw_string.substr(found + 1, raw_string.size());
},
arg_head
);
or, if you want both branches:
branch(std::is_arithmetic<T>{},
[&](auto&& x){
raw_string = std::to_string(x); // blah blah
}, [&](auto&&) {
// else case
},
arg_head
);
and the first method only gets instantianted with x=arg_head if is_arithmetic is true.
Needs polish, but sort of neat.