I have a function taking a variadic parameter pack and at the beginning I want to check that all elements compare equal. Can I somehow use the new C++17 fold-expressions to write that succinctly as a one-liner? I was thinking
template<typename... Args>
void func (Args... args)
{
ASSERT ((args == ...));
// more code here...
}
but this doesn't work, as it compiles to code that first properly compares the last two arguments, but then compares the third-last argument to the result of the first comparison, which is a bool. What use-case could this type of fold expression possibly have (similar for args < ...)? Is there any chance I can avoid writing a dedicated recursive template to do this?
The reason that doesn't work, unfortunately, is due to the fact that boolean operators don't chain in C++ like they do in other languages. So the expression:
a == (b == c)
(what your fold-expression would expand to) would compare a to either true or false, nothing to do with what b or c actually are. I was hoping the operator<=> would add chaining but apparently that part was dropped.
The fixes are that you have to break up the comparisons:
(a == b) && (b == c)
Of course that doesn't lend itself to folding very well, but you could instead compare everything to the first element:
(a == b) && (a == c)
Which is to say:
((a0 == args) && ... )
At that point, we just need to be able to pull out the first element. No problem, that's obviously what lambdas are for:
template <class... Args>
constexpr bool all_equal(Args const&... args) {
if constexpr (sizeof...(Args) == 0) {
return true;
} else {
return [](auto const& a0, auto const&... rest){
return ((a0 == rest) && ...);
}(args...);
}
}
As suggested by Piotr Skotnicki, a simple solution is separate the first argument from the followings and check it with using && as fold operator
By example, the following function that return true if all arguments are equals
template <typename A0, typename ... Args>
bool foo (A0 const & a0, Args const & ... args)
{ return ( (args == a0) && ... && true ); }
Unfortunately this can't work with an empty list of arguments
std::cout << foo(1, 1, 1, 1) << std::endl; // print 1
std::cout << foo(1, 1, 2, 1) << std::endl; // print 0
std::cout << foo() << std::endl; // compilation error
but you can add the special empty argument foo()
bool foo ()
{ return true; }
If, for some reason, you can't split the args in a a0 and the following args?
Well... you can obviously use the preceding foo() function (with special empty version)
template<typename... Args>
void func (Args... args)
{
ASSERT (foo(args));
// more code here...
}
or you can use the C++17 fold expression with comma operator and assignment as in the following bar()
template <typename ... Args>
bool bar (Args const & ... args)
{
auto a0 = ( (0, ..., args) );
return ( (args == a0) && ... && true );
}
Observe the initial zero in a0 assignment that permit the use of this solution also with an empty list of arguments.
Unfortunately, from the preceding auto a0 assignment I get a lot of warnings ("expression result unused", from clang++, and "left operand of comma operator has no effect", from g++) that I don't know how to avoid.
The following is a full working example
#include <iostream>
template <typename A0, typename ... Args>
bool foo (A0 const & a0, Args const & ... args)
{ return ( (args == a0) && ... && true ); }
bool foo ()
{ return true; }
template <typename ... Args>
bool bar (Args const & ... args)
{
auto a0 = ( (0, ..., args) );
return ( (args == a0) && ... && true );
}
int main ()
{
std::cout << foo(1, 1, 1, 1) << std::endl; // print 1
std::cout << foo(1, 1, 2, 1) << std::endl; // print 0
std::cout << foo() << std::endl; // print 1 (compilation error
// witout no argument
// version)
std::cout << bar(1, 1, 1, 1) << std::endl; // print 1
std::cout << bar(1, 1, 2, 1) << std::endl; // print 0
std::cout << bar() << std::endl; // print 1 (no special version)
}
-- EDIT --
As pointed by dfri (thanks!), for and empty args... pack, the values for the following folded expressions
( (args == a0) && ... )
( (args == a0) || ... )
are, respectively, true and false.
So return instruction of foo() and bar() can be indifferently written
return ( (args == a0) && ... && true );
or
return ( (args == a0) && ... );
and this is true also in case sizeof...(args) == 0U.
But I tend to forget this sort of details and prefer to explicit (with the final && true) the empty-case value.
Here is how I do it in gcl library :
template <auto ... values>
constexpr static auto equal_v = []() consteval {
static_assert(sizeof...(values) > 0, "gcl::mp::value_traits::equal_v : no arguments");
constexpr auto first_value = std::get<0>(std::tuple{values...});
static_assert(
(std::equality_comparable_with<decltype(values), decltype(first_value)> && ...),
"gcl::mp::value_traits::equal_v : cannot compare values");
return ((values == first_value) && ...);
}();
or replacing static_assert by concept requirement :
template <typename ... Ts>
concept are_equality_comparable = requires(Ts ... values)
{
{
std::conditional_t<(std::equality_comparable_with<decltype(std::get<0>(std::tuple{values...})), decltype(values)> && ...), std::true_type, std::false_type>{}
} -> std::same_as<std::true_type>;
};
template <auto ... values>
requires(are_equality_comparable<decltype(values)...>)
constexpr static auto equal_v = []() consteval {
static_assert(sizeof...(values) > 0, "gcl::mp::value_traits::equal_v : no arguments");
constexpr auto first_value = std::get<0>(std::tuple{values...});
return ((values == first_value) && ...);
}();
Related
A few times in my program, I've had to check if a variable was one of many options. For example
if (num = (<1 or 2 or 3>)) { DO STUFF }
I've messed around with 'OR's, but nothing seems to be right. I've tried
if (num == (1 || 2 || 3))
but it does nothing.
I'd like to conveniently distinguish between several groups. For example
if (num = (1,2,3))
else if (num = (4,5,6))
else if (num = (7,8,9))
Here's a way in C++11, using std::initializer_list:
#include <algorithm>
#include <initializer_list>
template <typename T>
bool is_in(const T& v, std::initializer_list<T> lst)
{
return std::find(std::begin(lst), std::end(lst), v) != std::end(lst);
}
with that, you can do:
if (is_in(num, {1, 2, 3})) { DO STUFF }
It is not very efficient though when not used with built-in types. int will work fine, but if you compare std::string variables for example, the produced code is just awful.
In C++17 however, you can instead use a much more efficient solution that works well with any type:
template<typename First, typename ... T>
bool is_in(First &&first, T && ... t)
{
return ((first == t) || ...);
}
// ...
// s1, s2, s3, s4 are strings.
if (is_in(s1, s2, s3, s4)) // ...
The C++11 version would be very inefficient here, while this version should produce the same code as hand-written comparisons.
If the values you want to check are sufficiently small, you could create a bit mask of the values that you seek and then check for that bit to be set.
Suppose, you care about a couple of groups.
static const unsigned values_group_1 = (1 << 1) | (1 << 2) | (1 << 3);
static const unsigned values_group_2 = (1 << 4) | (1 << 5) | (1 << 6);
static const unsigned values_group_3 = (1 << 7) | (1 << 8) | (1 << 9);
if ((1 << value_to_check) & values_group_1) {
// You found a match for group 1
}
if ((1 << value_to_check) & values_group_2) {
// You found a match for group 2
}
if ((1 << value_to_check) & values_group_3) {
// You found a match for group 3
}
This approach works best for values that don't exceed the natural size your CPU likes to work with. This would typically be 64 in modern times, but may vary depending upon the specifics of your environment.
You have to do the comparison with each value. E.g.
if (num == 1 || num == 2 || num == 3) { stuff }
You may also want to consider a switch and intentionally falling through cases (although I don't think it's the best solution for what you're stating).
switch (num) {
case 1:
case 2:
case 3:
{DO STUFF}
break;
default:
//do nothing.
}
I just had a similar problem and I came to these C++11 solutions:
template <class T>
struct Is
{
T d_;
bool in(T a) {
return a == d_;
}
template <class Arg, class... Args>
bool in(Arg a, Args... args) {
return in(a) || in(args...);
}
};
template <class T>
Is<T> is(T d) {
return Is<T>{d};
}
Or as alternative without the recursion terminating method. Be aware that here the order of comparisons is undefined and that this does not terminate early if the first match is found. But the code is more compact.
template <class T>
struct Is {
const T d_;
template <class... Args>
bool in(Args... args) {
bool r{ false };
[&r](...){}(( (r = r || d_ == args), 1)...);
return r;
}
};
template <class T>
Is<T> is(T d) {
return Is<T>{d};
}
So for both solutions the code would look like:
if (is(num).in(1,2,3)) {
// do whatever needs to be done
}
You can define a set of integers, add the desired values to it, and then use the find method to see if the value in question is in the set
std::set<int> values;
// add the desired values to your set...
if (values.find(target) != values.end())
...
I needed to do something similar for enums. I have a variable and wish to test it against a ranges of values.
Here I've used a variadic template function. Note the specialisation for const char* type, so that is_in( my_str, "a", "b", "c") has the expected outcome for when my_str stores "a".
#include <cstring>
template<typename T>
constexpr bool is_in(T t, T v) {
return t == v;
}
template<>
constexpr bool is_in(const char* t, const char* v) {
return std::strcmp(t,v);
}
template<typename T, typename... Args>
constexpr bool is_in(T t, T v, Args... args) {
return t==v || is_in(t,args...);
}
Example usage:
enum class day
{
mon, tues, wed, thur, fri, sat, sun
};
bool is_weekend(day d)
{
return is_in(d, day::sat, day::sun);
}
float n;
if (n<1) exit(0);
if (n / 3 <= 1)
// within 1, 2, 3
else if (n / 3 <= 2)
// within 4, 5, 6
else if (n / 3 <= 3)
// within 7, 8, 9
I'm currently learning variadic template functions and parameter packing/unpacking.
This is my code,
template<typename T, typename U>
void my_insert(std::vector<int>& v, T& t) {
int i;
if (typeid(t).name() == typeid(const char*).name()) {
i = stoi(t);
}
else if (typeid(t).name() == typeid(char).name()) {
i = t - 48;
}
else if (typeid(t).name() == typeid(int).name()) {
i = t;
}
else if (typeid(t).name() == typeid(double).name()) {
i = static_cast<int>(round(t));
}
else if (typeid(t).name() == typeid(bool).name()) {
if (t) i == 1;
else i == 0;
}
else if (typeid(t).name() == typeid(std::vector<U>).name()) {
int j = 0;
while (j < t.size()) {
my_insert(v, t[j]);
++j;
}
}
else return;
v.push_back(i);
}
template<typename T, typename U, typename ...Args>
void my_insert(std::vector<int>& v, T& t, Args&... args) {
int i;
if (typeid(t).name() == typeid(const char*).name()) {
if (isdigit(t[0])) i = stoi(t);
// else do nothing
}
else if (typeid(t).name() == typeid(char).name()) {
i = t - 48;
}
else if (typeid(t).name() == typeid(int).name()) {
i = t;
}
else if (typeid(t).name() == typeid(double).name()) {
i = static_cast<int>(round(t));
}
else if (typeid(t).name() == typeid(bool).name()) {
if (t) i == 1;
else i == 0;
}
else if (typeid(t).name() == typeid(std::vector<U>).name()) {
int j = 0;
while (j < t.size()) {
my_insert(v, t[j]);
++j;
}
}
//else do nothing
v.push_back(i);
my_insert(args...);
}
int main() {
std::vector<int> v;
my_insert(v, "123", "-8", 32, 3.14159, true, true, false, '5', "12.3");
return 0;
}
ERROR : no instance of overloaded function my_insert matches the argument list
I don't understand what mistake I've made since for me the same exact implementation of the a print() function works with { cout << t << endl; print(args...); } , w/ signature <typename T, typename ...Args> void print(const T& t, const Args... args);
I know that a variadic function can be implemented with recursive calls non-variadic parameter overloaded versions of the same function. A so-called base case statement.
With all that being said, I'm unsure what it is that I'm doing incorrectly.
Well... there are some problems in your code.
The blocking error is the template parameter U
template<typename T, typename U>
void my_insert(std::vector<int>& v, T& t)
template<typename T, typename U, typename ...Args>
void my_insert(std::vector<int>& v, T& t, Args&... args)
The compiler can't deduce it and calling the function
my_insert(v, "123", "-8", 32, 3.14159, true, true, false, '5', "12.3");
the U isn't explicated
I suppose that the idea is "if T is a std::vector of some type U, add all element of the vector". If I understand correctly, I suggest to add a different overloaded version of the function.
Other problems...
1) In a couple of points you write something as
if (t) i == 1;
else i == 0;
It seems to me that your using operator == (comparison) instead of = (assignment).
General suggestion: enable the highest warning level to intercept this sort of trivial errors.
2) Your using typeid
if (typeid(t).name() == typeid(char).name())
to compare types.
Suggestion: use std::is_same instead
if ( std::is_same<T, char>::value )
3) The ground case of your recursion is a my_insert() function that is almost identical to the recursive version; the only differences are the absence of Args... argument and recursion call.
This is error prone because, if you modify one of the two version, you must remember to modify the other in the same way.
Suggestion: write a empty-and-do-nothing ground case; something as
void my_insert (std::vector<int> & v)
{ }
4) you can't compile
i = stoi(t);
when t isn't a char const *
Analogous problems with other assignments.
The problem is that when you write [pseudocode]
if ( condition )
statement_1;
else
statement_2;
the compiler must compile both statement_1 and statement_2 also when know compile-time that condition is true or false.
To avoid the compilation of the unused statement, you have to use if constexpr.
So you have to write something as
if constexpr ( std::is_same_v<T, char const *> )
i = std::stoi(t);
else if constexpr ( std::is_same_v<T, char> )
i = t - 48;
else if constexpr ( std::is_same_v<T, int> )
i = t;
else if constexpr ( std::is_same_v<T, double> )
i = static_cast<int>(std::round(t));
else if constexpr ( std::is_same_v<T, bool> )
i = t;
Unfortunately, if constexpr is available only starting from C++17.
Before C++17, you have to write different overloaded functions.
5) calling my_insert() recursively, you have to remember the v vector
my_insert(args...); // <-- wrong! no v
my_insert(v, args...); // <-- correct
6) take in count that "123" is convertible to char const * but isn't a char const * (it's a char const [4]); so, instead of
if constexpr ( std::is_same_v<T, char const *> )
i = std::stoi(t);
you can try with
if constexpr ( std::is_convertible_v<T, char const *> )
i = std::stoi(t);
The following is a possible C++17 implementation of your code
#include <cmath>
#include <string>
#include <vector>
#include <iostream>
void my_insert (std::vector<int> const &)
{ }
template <typename T, typename ... As>
void my_insert (std::vector<int> &, std::vector<T> const &, As const & ...);
template <typename T, typename ... As>
void my_insert (std::vector<int> & v, T const & t, As const & ... as)
{
int i{};
if constexpr ( std::is_convertible_v<T, char const *> )
i = std::stoi(t);
else if constexpr ( std::is_same_v<T, char> )
i = t - 48;
else if constexpr ( std::is_same_v<T, int> )
i = t;
else if constexpr ( std::is_same_v<T, double> )
i = static_cast<int>(std::round(t));
else if constexpr ( std::is_same_v<T, bool> )
i = t;
// else ???
v.push_back(i);
my_insert(v, as...);
}
template <typename T, typename ... As>
void my_insert (std::vector<int> & v, std::vector<T> const & t,
As const & ... as)
{
for ( auto const & val : t )
my_insert(v, val);
my_insert(v, as...);
}
int main ()
{
std::vector<int> v;
std::vector<char> u { '9', '8', '7' };
my_insert(v, "123", "-8", 32, 3.14159, true, u, false, '5', "12.3");
for ( auto const & val : v )
std::cout << val << ' ';
std::cout << std::endl;
}
I am using C++ for particle physics and I find myself commondly writing lines like this:
bool isItCorrect(int i){
if(i==11 || i == 62 || i==-11 || i == 11002 || i==22002) return True
else return false;
}
What is the easiest way for me to make this shorter in C++. In python I could do:
def isItCorrect( i ):
if (i is in [11,62,-11,11002,22002]) return True
else return False
You can use variadic templates in C++ 11 and define:
template <typename T, typename T1>
bool isItCorrect(T t, T1 t1) {
return t == t1;
}
template <typename T, typename T1, typename... T2>
bool isItCorrect(T t, T1 t1, T2... t2) {
return t == t1 || isItCorrect(t, t2...);
}
and use:
bool isItCorrect(int i) {
return isItCorrect(i, 62, -11, 110022, 22002);
}
It may not be the "simplest", but I generally just use a switch, eg:
bool isItCorrect(int i)
{
switch (i)
{
case 11:
case 62:
case -11:
case 11002:
case 22002:
return true;
default:
return false;
}
}
std::set provides a count function which works much like the is in from python.
bool isItCorrect(int i) {
return std::set<int>({11, 62, -11, 110022, 22002}).count(i);
}
You might be able to use something like this: You can wrap a std vector into a struct and then write a function that takes the value to check against the field. This will work as is if you know all the elements of the field a head of time.
#include <iostream>
#include <vector>
struct field {
std::vector<int> data { 11, 62, -11, 11002, 22002 };
};
bool isItCorrect( int i, const field& f ) {
for ( auto& d : f.data )
if ( d == i ) return true;
return false;
}
int main() {
field f;
std::cout << std::boolalpha;
std::cout << isItCorrect( 2, f ) << std::endl;
std::cout << isItCorrect( 62, f ) << std::endl;
std::cout << "\nPress any key to quit.";
std::cin.get();
return 0;
}
Output
false
true
Press any key to quit.
If you are working with arbitrary types, and you don't know how many elements to check against you can template it as such using variadic function templates and parameter packs:
fieldT.h
#ifndef FIELD_T_H
#define FIELD_T_H
#include <vector>
#include <type_traits>
template<class T>
class fieldT {
private:
std::vector<T> _data;
public:
explicit fieldT( std::vector<T>& data ) : _data( data ) {}
template<class... Args>
fieldT( Args&&... args ) :
_data { std::forward<Args>( args )... } {}
std::vector<T> data() {
return _data;
}
};
template<class T>
bool isItCorrectT( T t, fieldT<T>& f );
template<class T, class... Args>
bool isItCorrectT( T t, Args... args );
#include "fieldT.inl"
#endif // FIELD_T_H
fieldT.inl
template<class T>
bool isItCorrectT( T t, fieldT<T>& f ) {
for ( auto& d : f.data() )
if ( d == t ) return true;
return false;
}
template<class T, class... Args>
bool isItCorrectT( T t, Args... args ) {
fieldT<T> f( args... );
for ( auto& d : f.data() )
if ( d == t ) return true;
return false;
}
Here I turned the class into a class template and then I also template the function isItCorrect only I created 2 overloads. One that will accept the value to check against and the fieldT<T> object where the 2nd overload will accept the value to check against and any arbitrary amount of arguments for that type.
Here is a demo of using the above class template using both overloaded methods
for each constructor of the class:
#include <iostream>
#include <vector>
#include "fieldT.h"
int main() {
std::cout << std::boolalpha;
// First create a vector of floats and instantiate a field<float> passing in that vector.
std::vector<float> floats { 1.0f, 1.1f, 1.2f, 1.3f, 1.4f };
fieldT<float> ff( floats );
// checking for both true and false case using the fieldT<T> above
std::cout << isItCorrectT( 2.0f, ff ) << std::endl;
std::cout << isItCorrectT( 1.2f, ff ) << std::endl;
// Or we can pass the values directly into the function as the
// overloaded version will take all but the first parameter
// and create a field<T> object populating its internal vector.
std::cout << isItCorrectT( 1.5f, 2.9f, 7.5f, 3.4f ) << std::endl;
std::cout << isItCorrectT( 1.5f, 2.9f, -3.7f, 1.5f, 8.9f ) << std::endl;
std::cout << "\nPress any key to quit.";
std::cin.get();
return 0;
}
Output
false
true
false
true
Press any key to quit.
What is real nice about this is that it remains generic and portable and can be used for any arbitrary type <T> with any amount of arguments of <T>.
I did not add in any tests to make sure that the values after the first are of type <T> but one should be able to easily assert that.
As you can see from within the main. The actual function call is very clean, easy to read and use. Even the class and the functions that operate on it are very clean and readable, easy to follow and understand and most important they are generic and portable, reusable.
bool isItCorrect(int i)
{
set<int> correctNums { 11, 62, -11, 11002, 22002 };
return correctNums.find(i) != correctNums.end();
}
I believe it's the simplest way.
I have constexpr function which counts number of placeholders https://godbolt.org/g/JcxSiu,
e.g: "Hello %1" returns 1, and "Hello %1, time is %2" returns 2.
Then I would like to make a function which does not compile if number of arguments does not equal to number of placeholders.
template <typename... Args>
inline std::string make(const char* text, Args&&... args) {
constexpr static unsigned count = sizeof...(args);
// TODO how to compile time check if count == count_placeholders(text)
// constexpr static auto np = count_placeholders(text);
//static_assert(count == np;, "Wrong number of arguments in make");
return std::to_string(count);
};
so that
make("Hello %1", "World"); compiles and
make("Hello %1 %2", "World"); or make("Hello %1", "World", "John"); does not.
I think it can be done, I just don`t know how. Maybe with some template magick :)
EDIT
I almost get what I want. https://godbolt.org/g/Y3q2f8
Now aborts in debug mode. It is possible to make compile time error?
My first idea was to enable/disable make() using SFINAE; something like
template <typename... Args>
auto make(const char* text, Args&&... args)
-> std::enable_if_t<sizeof...(args) == count_placeholders(text),
std::string>
{
// ...
}
Unfortunately this didn't compile because text can't be used in a constexpr.
But, if you accept that text is a template argument (so known at compile time), you can do something as
template <char const * const text, typename ... Args>
auto makeS (Args && ... args)
-> std::enable_if_t<sizeof...(args) == count_plc(text), std::string>
{ return std::to_string(sizeof...(args)); }
The following is a full working example
#include <string>
#include <type_traits>
constexpr std::size_t count_plc (char const * s,
std::size_t index = 0U,
std::size_t count = 0U)
{
if ( s[index] == '\0' )
return count;
else if ( (s[index] == '%') && (s[index+1U] != '\0')
&& (s[index+1U] > '0') && (s[index+1U] <= '9') )
return count_plc(s, index + 1U, count+1U);
else
return count_plc(s, index + 1U, count);
}
template <char const * const text, typename ... Args>
auto makeS (Args && ... args)
-> std::enable_if_t<sizeof...(args) == count_plc(text), std::string>
{ return std::to_string(sizeof...(args)); }
constexpr char const h1[] { "Hello %1" };
constexpr char const h2[] { "Hello %1 %2" };
int main ()
{
makeS<h1>("World"); // compile
//makeS<h2>("World"); // compilation error
//makeS<h1>("World", "John"); // compilation error
}
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.