I am experimenting to change some of the current code in a library that returns an enum for status code to a class type that has status and sub-status as shown below in status class below. One of the requirements is to have this work with lot of existing code that uses the type in == and != kind of checks all over the code base. Another requirement is to be able to use it existing printf statements all over the place.
I converted the enum to #define as below and then used operator overloading for == (will have to implement inequality later). I was expecting the printf() usage shown below to fail when I try to print status. However, surprisingly that seems to be working and printing the status_ member field value already !! How is it working ? Can someone please help make it make sense ?
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <time.h>
// these are the status codes that any function can return
// for example test_function() below returns one of these
#define STATUS_OK 100
#define STATUS_ERR 200
// this is the new class that replaces the enum types
class status {
public:
status() {}
status(unsigned int status) : status_(status) {}
status(unsigned int status, unsigned int sub_status) : status_(status), sub_status_(sub_status) {}
bool operator==(const status& other) {
return (status_ == other.status_);
}
private:
unsigned int status_;
unsigned int sub_status_; // sub_status_ is meant for future usage
};
// helper function to test the main code
// this returns possible status codes
status
test_function (void)
{
int r1, r2;
r1 = rand();
r2 = rand();
if (r1 > r2) {
return STATUS_OK;
}
return STATUS_ERR;
}
int
main ()
{
status ret;
srand(time(0));
ret = test_function();
printf("ret is %u\n", ret); // how is this already working ?? %u should expect unsigned int ??
if (ret == STATUS_OK) {
printf("ret is OK\n");
} else {
printf("ret is not OK\n");
}
return 0;
}
A sample runs print the following:
# ./a.out
ret is 200. <== what makes this work ? how is class type getting converted to integer?
ret is not OK
# ./a.out
ret is 100
ret is OK
As a follow up question, is there anything in the status class that I can do to make printf() legitimately work work this way ? This is convenient as I can avoid touching lot of code.
CodePudding user response:
printf
's %u
specifier expects an unsigned int
. Passing it a different type as argument (after default argument promotions) causes undefined behavior.
That is the case here.
printf
is not extendable. You can either write your own function wrapping printf
and interpreting the format string (not recommended) or use std::cout <<
with overloaded operator<<
instead.
Or you can use fmtlib as alternative (also available as <format>
in C 20).
CodePudding user response:
You can't make your class work with printf by changing only the class, since it's a C language function and not C , that interprets your class in memory as a simple collection of bytes without any internal structure (void*), i.e. it doesn't call conversion operators to type. Your code works because the class is two consecutive unsigned int fields status_ and sub_status_ . printf, seeing the format flag "u", takes the zero offset of the class field with the size and type of unsigned int, which completely coincides with the status_ field. If you add another field in the class before this field, for example "unsigned int constant_ = 5;", then 5 will always be displayed, since now it will be located at the beginning. If the field size does not match the printf output format or a virtual method table appears in the class, then your output can be anything.
To avoid this, you must explicitly define how to print your class, in order to do this:
use
std::cout
, in other words replaceprintf("ret is %u\n", ret);
withstd::cout << "ret is " << ret << std::endl;
add a public method in the class to get the status code:
unsigned int status_value() const { return status_; }
add a function after the class declaration to define the output of your class to the output stream:
std::ostream& operator<<(std::ostream& stream, const status& s) { stream << s.status_value(); return stream; }
Then you will have a predictable result.
If you want minimal work on your printf calls, you can add only a conversion operator to unsigned int in the class:
operator unsigned int() const
{
return status_;
}
Then you only have to cast your class to unsigned int:
printf("ret is %u\n", (unsigned int)ret);
printf("ret is %u\n", static_cast<unsigned int>(ret));
Or add and use a public method to get the status code:
printf("ret is %u\n", ret.status_value());