I am on the task to migrate the concept of error handling in a C++ class library. Methods that previously simply returned bool (success/fail) shall be modified to return a Result
object which conveys a machine readable error code and a human readable explanation (and some more which does not matter here).
Walking through thousands of lines of code is error prone, therefore I try to get the best support from the compiler for this task.
My result class has - among other member methods - a constructor that constructs the result from a code and an assignment operator for the code:
class Result
{
public:
typedef unsigned long ResultCode;
explicit Result(ResultCode code); // (1)
Result& operator=(ResultCode code); // (2)
};
Remark: I would usually use an enum class for ResultCode
which would solve my problems, but this is not an option. This is because the major design objective was to use Result
in different libraries, each of which shall define its own set of result codes without requiring one big header file that defines all possible result codes for all libraries. In fact, each class shall be able to define local result codes so that the list of possible result codes can be obtained from the classes header. Thus the codes cannot be enumerated in Result
, they must be defined by the classes using the Result
class.
To avoid implicit conversions on
return true;
Statements in the client code, the constructor has been declared explicit. But in nesting method calls, another problem occurs. Say, I have a method
bool doSomething()
{
return true;
}
Which I am using in a function that returns a Result
object. I want to forward result codes of nested calls
Result doSomethingElse
{
Result result = doSomething();
return result;
}
With the current implementation of Result
's assignment operator, this is not going to give me a compiler error - the boolean return value of doSomething() is implicitly converted to unsigned long.
As I have read in the C++ documentation, only constructors and conversion operators may be declared explicit.
My questions
Your problem isn't in the class Result
: you are explicitly creating a new instance of it, after all; explicit
cannot forbid it.
I don't think you can forbid the implicit promotion bool -> long
.
You can work around it. One way is to make ResultCode
not be an integer type. then, it could have an explicit constructor. Something like
class ResultCode
{
unsigned long m_code;
public:
explicit ResultCode(unsigned long code) : m_code(code) {}
operator unsigned long () { return m_code; }
};
would allow you to use ResultCode
anywhere you can use a unsigned int
and create it as ResultCode res = 5
or return ResultCode(5)
but not call a function expecting a ResultCode
(such as the Result
constructor!) with anything which is not a ResultCode
already, nor do something like return 5
if the function must return a ReturnCode
.
Otherwise you can use template overloadng to 'catch' anything not being an unsigned int
and force it to be an error
typedef unsigned long ResultCode;
class Result
{
ResultCode m_par;
public:
template<typename T>
Result(T param) { static_assert(false); }
template<>
Result(ResultCode par): m_par(par) {}
};
int main()
{
ResultCode a = 5; //ok
//unsigned long a = 6; //also ok
//bool a = true; //error!
//int a = 7; //also error!!
Result b(a);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With