C Variadic function in a C++ wrapper - c++

I am rewriting a C wrapper around a C Python API (Python 1.5) and I noticed that the function Py_VaBuildValue uses variadic number of args. I wondered if I have to use the same in my C++ function wrapper to pass to this function or if there is a more C++ way to deal with this?
I know variadic functions can be the cause of untold trouble, so I'd rather avoid having to use if there is a better way.
EDIT:
So here is the C code I need to make into a C++ function:
int Set_Global(char *modname, char *varname, char *valfmt, ... /* cval(s) */) {
int result;
PyObject *module, *val; // "modname.varname = val"
va_list cvals;
va_start(cvals, valfmt); // C args after valfmt
module = Load_Module(modname); // get/load module
if (module == NULL)
return -1;
val = Py_VaBuildValue(valfmt, cvals); // convert input to Python
va_end(cvals);
if (val == NULL)
return -1;
result = PyObject_SetAttrString(module, varname, val);
Py_DECREF(val); // set global module var
return result; // decref val: var owns it
}
So I'm making the same function with std::string instead of char* and I want to change the ellipsis to something more c++ like, that I can however then pass to Py_VaBuildValue inside the function.

If you want to be clever and don't fear some heavy template wizardry, it should be possible to generate (or massage) valfmt to always match the types you want to pass (I am assuming it uses format specifiers similar to printf, but the technique is applicable to any kind of format specification). You could do something like:
template <typename T> struct format_trait;
template <> struct format_trait<int> { static const char * format() { return "%i"; }};
template <> struct format_trait<unsigned> { static const char * format() { return "%u"; }};
... // and so on for each type you want to use
template <typename Arg1>
int Set_Global(const std::string &modname, const std::string &varname, const Arg1 &arg1)
{
return ::Set_Global(modname.c_str(), varname.c_str(), format_trait<Arg1>::format(), arg1);
}
template <typename Arg1, typename Arg2>
int Set_Global(const std::string &modname, const std::string &varname, const Arg1 &arg1, const Arg2 &arg2)
{
return ::Set_Global(modname.c_str(), varname.c_str(),
std::string(format_trait<Arg1>::format()) + format_trait<Arg2>::format(),
arg1, arg2);
}
... // Repeat up to number of argument you reasonably expect to need or use C++0x variadic templates.
This is the simple way where each value is formatted the default way and combined together. If you want something more complex, you can create a function, that will get valfmt string and correct format specifiers (obtained from the trait) and will fix up the format string to match.

You may write template function which will check the correctness of params, and won't allow your program to crash.
bool BuildValueCheck(const char * s, int v)
{
if( s[0] == 'i' )
return true;
return false;
}
bool BuildValueCheck(const char * s, float v)
{
if( s[0] == 'f' )
return true;
return false;
}
bool BuildValueCheck(const char * s, char * v)
{
if( s[0] == 's' || s[0] == 'z' )
return true;
return false;
}
// and so on for each other type
template<typename t1>
PyObject *BuildValue(char * format, t1 v1)
{
char * s = strchr(format, "ifsz...."); // Skip here all "()[]" etc
if( !s )
return NULL; // and print an error
if(!BuildValueCheck(s, v1))
return NULL; // and also print an error
return Py_BuildValue(format, v1);
}
template<typename t1, typename t2>
PyObject *BuildValue(char * format, t1 v1, t2 v2)
{
// Skip here all "()[]" etc
char * s = strchr(format, "ifsz....");
if( !s )
return NULL;
if(!BuildValueCheck(s, v1))
return NULL;
s = strchr(s+1, "ifsz....");
if( !s )
return NULL;
if(!BuildValueCheck(s, v2))
return NULL;
return Py_BuildValue(format, v1, v2);
}
// and so on for 3,4,5 params - I doubt your program uses more
// and then replace all Py_BuildValue with BuildValue across the code, or make a #define

Related

C++ Template is sending a "false argument" a bad practice?

std::stoi is throwing some errors in specific cases. I don't want to use try/catch block, so I googled a little bit about char convertion and saw that std::from_chars was doing exactly what I wanted, without those try/catch block.
Obviously, using std::from_chars directly works pretty well, but I don't like the syntax. So I started writing my own ToInt(), ToFloat(), etc. And was like : "ok, that sounds stupid, let's use template".
Here I am :
#include <string>
#include <charconv>
#include <iostream>
template <typename T>
T ToNumber(const char* str, T varType)
{
if (!str)
return 0;
T var = 0;
std::from_chars(str, str + strlen(str), var);
return var;
}
int main()
{
std::string t = "123.5";
auto a = ToNumber(t.c_str(), (int)0); // a will be an int (123)
float b = ToNumber(t.c_str(), (int)0); // b will store the value as int (123)
auto c = ToNumber(t.c_str(), (float)0); // c will be a float (123.5)
std::cout << a;
return 0;
}
It actually works, that is not the problem. But I was wondering... Is it a good practice to send a "false argument" (T varType) in order to get the cast done and enable auto ?
If not, what is the clever way to write such a function ?
Thanks in advance
You are not checking the return value of std::from_chars for failure, eg:
auto [p, ec] = std::from_chars(str, str + strlen(str), var);
if (ec != std::errc()) {
...
}
That said, another way to deal with the template argument T without using a type-casted input parameter is to simply specify the desired type explicitly at the call site instead, eg:
template <typename T>
T ToNumber(const char* str)
{
if (!str)
return T{};
T var{};
auto [p, ec] = std::from_chars(str, str + strlen(str), var);
if (ec != std::errc())
return T{};
return var;
}
auto a = ToNumber<int>(t.c_str());
float b = ToNumber<int>(t.c_str());
auto c = ToNumber<float>(t.c_str());
Otherwise, you can use template argument deduction via a reference output parameter (just like std::from_chars() does), eg:
template <typename T>
bool ToNumber(const char* str, T &var)
{
if (!str)
return false;
auto [p, ec] = std::from_chars(str, str + strlen(str), var);
return (ec == std::errc());
}
int a;
ToNumber(t.c_str(), a);
int tmp;
ToNumber(t.c_str(), tmp);
float b = tmp;
float c;
ToNumber(t.c_str(), c);

How to get function pointer from many functions and pass args to it?

I have many defined functions as follows, all return an int
int fn1(int x) {
return x;
}
int fn2(std::string x, int y, std::string z) {
// process string x and z
int x1 = process(x);
int z1 = process(z);
return x1 + y + z1;
}
// ... and many more similar functions
For some reasons, I need to implement a wrapper to call the above functions by function names,
int wrapper(std::string fn_name, some_struct_t data, std::vector<std::string> field_names) {
a_fn_ptr_type fn_ptr = nullptr; // <q1>: is this a right way to do?
// by fn_name, decide which fn to call
if (fn_name == "fn1") {
fn_ptr = &fn1;
}
if (fn_name == "fn2") {
fn_ptr = &fn2;
}
...
// given field_names, get the field from data, pass them to fn_ptr as args
for (auto field_name: field_names) {
std::any value = get_from_data(data, field_name, field_type); // field_type will be updated by this call, so that we know the value type.
// <q2>: but how to pass each value as arg to fn_ptr here?
}
}
The above code demonstrate what I want to achieve, and I have 2 questions (as pointed by <q1> and <q2>).
I'm not sure if the code is the right way to go, hope to get some advice from people, thanks!
Inspired by the comments:
A wrapper that takes some_struct_t data, std::vector<std::string> field_names. Assuming a
template <typename T>
T get_from_data(some_struct_t, std::string);
You have a function type
using func_t = std::function<int(const some_struct_t &, const std::vector<std::string>&)>;
which you can instantiate from functions via
template <typename... Args, size_t... Is>
auto wrap_impl(int(*func)(Args...), std::index_sequence<Is...>)
{
return [func](const some_struct_t & data, const std::vector<std::string>& field_names)
{ return func(get_from_data<Args>(data, field_names.at(Is))...); };
}
template <typename... Args>
func_t wrap(int(*func)(Args...))
{
return wrap_impl(func, std::index_sequence_for<Args...>{});
}
and then you can have a
std::map<std::string, func_t> functions;

Replace varargs with variadic templates

I'm using C++17 with templates and recursion to replace the C Va_Args.
Currently only floats are supported, more types are following once float is working ;)
class CWrite
{
public:
template<typename NextT, typename ...RestT>
static std::string Format(NextT next, RestT ... rest);
private:
template<typename T>
static constexpr bool is_float = std::is_same_v<T, float>;
template<typename T>
static constexpr bool IsValidParam();
template<typename LastT>
static std::string Format(LastT last);
///Empty param case
static std::string Format();
};
// +++++++++++++++++++ Implementation ++++++++++++++++++++++++++
template<typename T>
constexpr bool CWrite::IsValidParam()
{
bool bRes = false;
bRes |= is_float<T>;
return bRes;
}
template<typename NextT, typename ...RestT>
std::string CWrite::Format(NextT next, RestT ... rest)
{
std::string strRes = Format(next);
strRes += Format(rest...);
return strRes;
}
template<typename LastT>
std::string CWrite::Format(LastT last)
{
std::string strRes;
if (is_float<LastT>)
{
strRes = "float:";
char buffer[10] = { };
snprintf(buffer, 10, "%f", last);
strRes += buffer;
}
return strRes;
}
///Empty param case
std::string CWrite::Format()
{
return "";
}
calling this with
std::string strRes = CWrite::Format(1.0f, 2.0f, 3.0f, 4.0f, 5);
results in a warning for snprintf()
format '%f' expects argument of type 'double', but argument 4 has type 'int'
I'd expect that IsValidParam returns false for the last argument which should be an int.
https://onlinegdb.com/B1A72GHgU
Could you help me out here?
Did i miss something here?
If you can use C++17, you should use if constexpr in the following function
template<typename LastT>
std::string CWrite::Format(LastT last)
{
std::string strRes;
// VVVVVVVVV <-- add "constexpr" here
if constexpr (is_float<LastT>)
{
strRes = "float:";
char buffer[10] = { };
snprintf(buffer, 10, "%f", last);
strRes += buffer;
}
return strRes;
}
The problem is that, using a simple if instead if constexpr, the compiler has to compile the statement (the part inside the { ... }) also when is_float<LastT> is false.
If you can't use C++17... I suppose you can differentiate the function through overloading
std::string CWrite::Format (float last)
{
std::string strRes { "float:" };
char buffer[10] = { };
snprintf(buffer, 10, "%f", last);
return strRes += buffer;
}
std::string CWrite::Format (int last)
{
std::string strRes { "int:" };
char buffer[10] = { };
snprintf(buffer, 10, "%i", last);
return strRes += buffer;
}
max66's answer addresses the reason why your method has a problem with the format string and how to fix it. Basically you just need some way of picking a different format string based on the type of the value being formatted.
However, I'd like to point out another flaw: you assume that any given value will only require 9 characters to convert into a string. For very large values (e.g. 1e22) this will fail. GCC will actually issue you a warning if it can determine this at compile time.
Additionally, your current implementation allocates many strings and recursively appends them together. This is - of course - highly inefficient and diminishes the speed of the printf family of functions to the point that it's not really worth using them.
Also your solution doesn't check for format errors (snprintf() returns negative in this case). And in such cases you may be appended undefined memory onto your string, as I'm not certain the C standard guarantees to null terminate the buffer on failure cases (but it might).
My solution is to have a function that formats a given argument in-place onto the end of a std::string. Additionally it handles format errors and cases where 9 bytes is insufficient to hold the formatted value.
Additionally I impose SFINAE restrictions on the argument types to ensure it can only be called with types that we support.
Here's my solution with comments to explain what does what and why:
#include <string>
#include <type_traits>
#include <iostream>
// checks if T is a type we support
template<typename T>
inline constexpr bool allowed_type = std::is_floating_point_v<T> || std::is_integral_v<T>;
// the initial amount of space for stringifying each argument
constexpr std::size_t APPEND_PADDING = 20;
// returns the appropriate format string for type T (T assumed to be supported)
template<typename T>
const char *fmt_string()
{
if constexpr (std::is_floating_point_v<T>) return "%f";
else return "%d";
}
// stringifys val onto the end of str (T assumed to be supported)
template<typename T>
void append(std::string &str, T val)
{
std::size_t prev_size = str.size(); // remember the previous size of str
str.resize(prev_size + APPEND_PADDING); // allocate the space we need
const char *fmt = fmt_string<T>(); // get the format string to use
// format the value and check the save the return value
int res = snprintf(&str[prev_size], APPEND_PADDING, fmt, val);
// on format error, just skip it (or )
if (res < 0) str.resize(prev_size);
// if we didn't have enough room we need to try again with the correct size
if ((std::size_t)res >= APPEND_PADDING)
{
str.resize(prev_size + res + 1); // make space for the characters we need and the null terminator
snprintf(&str[prev_size], res + 1, fmt, val); // format the string again (this time will work)
str.pop_back(); // remove the null terminator
}
// otherwise we had enough room, so just truncate to the written characters
else str.resize(prev_size + res);
}
// formats all of args into a single string (only allows supported types)
template<typename ...Args, std::enable_if_t<(allowed_type<Args> && ...), int> = 0>
std::string format(Args ...args)
{
std::string str; // create an empty buffer string to store the result
str.reserve(sizeof...(args) * APPEND_PADDING); // predict how much space we'll need for everything
int _[] = { (append(str, args), 0)... }; // append all the args to str one at a time
(void)_; // suppress unused variable warnings (will just be optimized away)
return str;
}
int main()
{
std::cout << format(1, 2, 2.3, 3, 4.4, 5, 1e22) << '\n';
}
Note that this runs all the formatted strings together with no separation. Fixing this would be as simple as changing the format strings returned from fmt_string().
I used different function names than you did, but you get the idea. format() is the function you would use.

C++ Reduce Redundancy

I have many C++ functions in a DLL that I made specifically for Excel to call.
I frequently pass to these functions as parameters OLE VARIANTs that contain SafeArrays.
I wrote a function to act as a guard to ensure that a passed VARIANT actually contains a SafeArray and that the array is of the proper type for the occasion, both in terms of datatype and number of dimensions.
If those three criteria are satisfied the function returns a pointer to the first element and also has two out parameters to return the number of elements in each dimension (note: I only care about 1d and 2d SafeArrays).
Here is the function:
PVOID SafeArrayDataPointer(VARIANT &v, const long &ArrayType, const long &ArrayDims, long &Elems1D, long &Elems2D) {
SAFEARRAY* pSafeArray = NULL;
if ( V_VT(&v) & VT_ARRAY ) {
if ( ArrayType != (V_VT(&v) & VT_TYPEMASK) ) return NULL;
pSafeArray = V_ARRAY(&v);
if ( ArrayDims != pSafeArray->cDims ) return NULL;
switch (ArrayDims) {
case 2:
Elems1D = (pSafeArray->rgsabound)[1].cElements;
Elems2D = (pSafeArray->rgsabound)[0].cElements;
break;
case 1:
Elems1D = (pSafeArray->rgsabound)[0].cElements;
Elems2D = 0;
break;
default:
Elems1D = 0;
Elems2D = 0;
break;
}
return pSafeArray->pvData;
} else return NULL;
}
This function works well and allows me to conveniently grab the data pointer and to get the number of elements in each dimension by calling like this (assuming vData is a VARIANT passed from Excel:
pDataArray = (VARIANT*) SafeArrayDataPointer(vData, VT_VARIANT, 2, Elems1D, Elems2D); if (pDataArray == NULL) goto error1;
However, there are two things that I do not like about this:
1.) Since the function returns a PVOID I have to cast to the relevant pointer type... but this is redundant as I've already specfied in the second argument what type of array must be contained in the VARIANT. For example in a different situation I may need to make sure that the array is of long values:
pDataArray = (long*) SafeArrayDataPointer(vData, VT_I4, 1, Elems1D, Junk); if (pDataArray == NULL) goto error1;
Again, this works fine, but it is redundant. I would much prefer some mechanism that allows the function to return the correct type of pointer. And I'm hoping this can be done without templates.
2.) I can't figure out how to NOT have the Junk parameter in the second example where I specify that the array must be 1d. Obviously, in such a case, there are no elements in the second dimension.
If you don't want to use templates, and you don't want specialized functions, you could use a gross macro to solve the first problem about having to cast from PVOID every time you call SafeArrayVariantPointer:
Something like this:
#define CAST_TYPE_VT_VARIANT (VARIANT *)
#define CAST_TYPE_VT_LONG (long *)
#define SAFE_ARRAY_DATA_POINTER(vData, vType, dimSize, elems1D, elems2D) \
CAST_TYPE_##vType SafeArrayDataPointer((vData), (vType), (dimSize), (elems1D), (elems2D))
Then you can call like:
VARIANT *pDataArray = SAFE_ARRAY_DATA_POINTER(vData, VT_VARIANT, 2, Elems1D, Elems2D);
But first you need to change your method signature so the ArrayType argument is taken as a long not a const long &.
Note this assumes the second parameter to SAFE_ARRAY_DATA_POINTER macro must be a literal that corresponds to one of your defined CAST_TYPE_* macros. It can't be a variable.
For you second question about the redundant Junk parameter, you can create an overloaded SafeArrayDataPointer function which only returns the size of first dimension. It can call the first version of SafeArrayDataPointer and discard the size of the second dimension.
Something like:
PVOID SafeArrayDataPointer(VARIANT &v, long ArrayType, const long &ArrayDims, long &Elems1D)
{
long Elems2D;
PVOID *toReturn = SafeArrayDataPointer(v, ArrayType, ArrayDims, Elems1D, Elems2D);
if (Elems2D != 0) toReturn = NULL;
return toReturn;
}
However, to solve this problem, I would probably use templates.
First, create a set of array_type_traits classes which expose a typedef for your cast type given a long representing VT_LONG, VT_VARIANT, etc.
//Generic array_type_traits class
template<long array_type>
class array_type_traits
{
public:
typedef PVOID cast_type;
};
//Specialized for VT_LONG
template<>
class array_type_traits<VT_LONG>
{
public:
typedef long * cast_type;
};
//Specialized for VT_VARIANT
template<>
class array_type_traits<VT_VARIANT>
{
public:
typedef VARIANT * cast_type;
};
Continue to specialize these for each VT_* type you have.
Next, encapsulate your SafeArrayDataPointer function inside a class SafeArrayDataPointerBase.
//Base class which has one static function Get() that returns a PVOID
class SafeArrayDataPointerBase
{
protected:
static PVOID Get(VARIANT& vData, long vType, long dimSize, long& elems1D, long& elems2D)
{
// Place your SafeArrayDataPointer function code here
}
};
Now create your class which will call `SafeArrayDataPointerBase::Get() and then cast the result to the correct type.
template<long ArrayType>
class SafeArrayDataPointer : public SafeArrayDataPointerBase
{
public:
typedef typename array_type_traits<ArrayType>::cast_type cast_type;
static cast_type Get(VARIANT& v, long ArrayDims, long& Elems1D, long& Elems2D)
{
return (cast_type) SafeArrayDataPointerBase::Get(v, ArrayDims, ArrayType, Elems1D, Elems2D);
}
};
And finally, you would call the template class as so:
VARIANT *vp = SafeArrayDataPointer<VT_VARIANT>::Get(v, ArrayDims, Elems1D, Elems2D);
long *vl = SafeArrayDataPointer<VT_LONG>::Get(v, ArrayDims, Elems1D, Elems2D);
Smth like this:
template<int vt> struct vt_type_disp;
template<> struct vt_type_disp<VT_I4> { typedef LONG type; };
template<> struct vt_type_disp<VT_VARIANT> { typedef VARIANT type; };
template<int vt> using vt_type = typename vt_type_disp<vt>::type;
template<int vtElem>
auto unpack_array(VARIANT& v, ULONG& d1_size)
{
if ((V_VT(&v) & VT_ARRAY) && (V_VT(&v) & VT_TYPEMASK) == vtElem)
{
SAFEARRAY* p = (V_VT(&v) & VT_BYREF) ? *V_ARRAYREF(&v) : V_ARRAY(&v);
if (p->cDims == 1)
{
d1_size = p->rgsabound[0].cElements;
return static_cast<vt_type<vtElem>*>(p->pvData);
}
}
return static_cast<vt_type<vtElem>*>(nullptr);
}
template<int vtElem>
auto unpack_array(VARIANT& v, ULONG& d1_size, ULONG& d2_size)
{
if ((V_VT(&v) & VT_ARRAY) && (V_VT(&v) & VT_TYPEMASK) == vtElem)
{
SAFEARRAY* p = (V_VT(&v) & VT_BYREF) ? *V_ARRAYREF(&v) : V_ARRAY(&v);
if (p->cDims == 2)
{
d1_size = p->rgsabound[1].cElements;
d2_size = p->rgsabound[0].cElements;
return static_cast<vt_type<vtElem>*>(p->pvData);
}
}
return static_cast<vt_type<vtElem>*>(nullptr);
}
// functions to export from dll (if you need them)
auto unpack_array_I4_1D(VARIANT &v, ULONG& dim_size) { return unpack_array<VT_I4>(v, dim_size); }
auto unpack_array_I4_2D(VARIANT &v, ULONG& d1_size, ULONG& d2_size) { return unpack_array<VT_I4>(v, d1_size, d2_size); }
auto unpack_array_VARIANT_1D(VARIANT &v, ULONG& dim_size) { return unpack_array<VT_VARIANT>(v, dim_size); }
auto unpack_array_VARIANT_2D(VARIANT &v, ULONG& d1_size, ULONG& d2_size) { return unpack_array<VT_VARIANT>(v, d1_size, d2_size); }
// etc
Note: your code doesn't handle VT_BYREF properly

How to get a fully-qualified function name in C++ (gcc), _excluding_ the return type?

This question describes how to use __PRETTY_FUNCTION__ to get a full name of a function, including its return type, argument types, namespace and template parameters.
Consider the following, beautiful function:
namespace foo {
namespace {
template<int i>
int (*bar(int (*arg)(int *)))(int *) {
printf("%s\n", __PRETTY_FUNCTION__);
return arg;
}
} // anonymous namespace
} // namespace foo
If it's not obvious to you, the function takes, and returns, a pointer to an int * -> int function.
Its pretty name is, when compiled with g++ (4.9),
int (* foo::{anonymous}::bar(int (*)(int*)))(int*) [with int i = 1337]
and, with clang++ (3.5),
int (*foo::(anonymous namespace)::bar(int (*)(int *)) [i = 1337])(int *)
Those strings are pretty unsuitable for testing whether the function is part of a certain namespace. Is there any other way, or, say, a compiler-provided library to parse those strings?
To clarify, I'd rather have something like
foo::{anonymous}::bar <I don't care about anything beyond this point>
Even more ideally, I'd like a compile-time way, such as a constexpr function split(__PRETTY_FUNCTION__) that yields some sort of list of
fully qualified function name
return type
type of arg0
type of arg1
etc, but I'd be happy with just the fully-qualified function name.
After a more careful observation I wrote this code:
template <typename InputIterator, typename T>
InputIterator findClosing( InputIterator first, InputIterator last, T close )
{
if (first == last)
return last;
auto open = *first;
unsigned counter = 1;
while (++first != last)
{
if (*first == close && --counter == 0)
return first;
if (*first == open)
++counter;
}
return last;
}
template <std::size_t N,
std::size_t N2>
std::string f(char const(&str)[N], char const(&name)[N2])
{
using namespace std;
// Argument to isalnum must be unsigned:
auto cond = [] (unsigned char c) {return !isalnum(c) && c != '_';};
auto iter = str;
for (;;++iter)
{
iter = search( iter, end(str),
begin(name), end(name)-1 );
if (iter == end(str))
throw invalid_argument("");
if ((iter == begin(str) || cond(iter[-1]))
&& (iter == end(str) - N2 || (cond(iter[N2-1]) && iter[N2-1] != ':')))
break;
}
auto origin_iter = iter;
while(iter != begin(str))
{
--iter;
for (auto p : {"()", "{}"})
if (*iter == p[1])
iter = findClosing(reverse_iterator<char const*>(iter+1),
reverse_iterator<char const*>(begin(str)),
p[0]).base()-2;
if (cond(*iter) && *iter != ':')
return string(iter+1, origin_iter+N2-1);
}
return string(iter, origin_iter+N2-1);
}
It should work with any function, assuming no unnecessary whitespace is existent in __PRETTY_FUNCTION__ and __func__ contains solely the unqualified function name.
Demo.
This doesn't do quite what you've asked in that it doesn't return a single value which is a constexpr string, but it gets close. It is, however, fully constexpr and it returns a pointer to the beginning of the namespace, and a pointer to the end of it (the beginning of the function name), and, optionally the length of that string.
constexpr bool isNotIdentifierChar(const char *pf)
{
return !isalnum(*pf) && *pf!='_';
}
constexpr const char* getNamespaceEnd(const char *pf, const char *func)
{
return (isNotIdentifierChar(pf) && 0==strncmp(&pf[1], func, strlen(func))
&& isNotIdentifierChar(pf+strlen(func)+1) && ':'!=pf[strlen(func)+1])
? &pf[1]
: getNamespaceEnd(++pf, func);
}
constexpr const char* getNamespaceStartIter(const char *pf, const char *end)
{
return (*pf==' ' && strchr(&pf[1], ' ') > end)
? &pf[1]
: getNamespaceStartIter(++pf, end);
}
constexpr const char* getNamespaceStart(const char *pf, const char *func)
{
return getNamespaceStartIter(pf, getNamespaceEnd(pf, func));
}
constexpr size_t getNamespaceSize(const char *pf, const char *func)
{
return getNamespaceEnd(pf, func) - getNamespaceStart(pf, func);
}
One can't have a constexpr return a std::string because the std::string (or any equivalent construct) has a non-trivial destructor, but we can return the start and end of the namespace like this:
printf("%s\n", std::string(
getNamespaceStart(__PRETTY_FUNCTION__, __func__),
getNamespaceEnd(__PRETTY_FUNCTION__, __func__)+strlen(__func__)
).data());
This version includes the function name ("bar" in this case), but it can also be omitted by simply leaving out the +strlen(__func__).
We could also make it somewhat cleaner by using a simple class:
class NamespaceString {
public:
NamespaceString(const char *pf, const char *func)
: start(getNamespaceStart(pf, func)),
end(getNamespaceEnd(pf, func)) {}
std::string getString() const {
return std::string(start, end);
}
private:
const char *start;
const char *end;
};
Then the use within the function is a bit cleaner:
static const NamespaceString ns(__PRETTY_FUNCTION__, __func__);
printf("%s\n", ns.getString().data());
Updated demo which includes both #Columbo's answer and this one: live code