In my environment I cannot use exceptions so I need an alternative solution for error-handling. Returning an int as the error code is not a good way in a new modern project because this interface prevents to return other data.
std::expected is not yet available; maybe there are some sample implementations but I need something already tested and robust.
I'm evaluating (boost::)outcome https://ned14.github.io/outcome/ and it seems to fit my needs: it has a clear interface and it should be very efficient if no auxiliary payloads are used.
The use case for generic class method is quite simple: https://ned14.github.io/outcome/tutorial/essential/result/
Regarding the usage with constructors, the author suggests a dual phase construction (https://ned14.github.io/outcome/tutorial/advanced/constructors/).
The tutorial does not talk about class composition and inheritance. The following example is the same as the tutorial.
class A {
protected: // use protected because of C class in the next example
constexpr A() noexcept { /*...*/ }
public:
~A() noexcept { /*...*/ }
A( A&& rhs ) noexcept { /*...*/ }
A& operator=( A&& rhs ) noexcept
{
this->~A();
new(this) A( std::move( rhs ) );
return *this;
}
// Remove copy ctor and assignment op
A( const A& ) = delete;
A& operator=( const A& ) = delete;
/** Static member constructor */
static result<A> A_ctor() noexcept
{
// Phase 1 ctor
A ret;
// Phase 2 ctor
if ( /*something goes wrong*/ ) return MyErrorCode::Error;
return { std::move( ret ) };
}
void a_method() noexcept { /*...*/ }
};
template<> struct make<A>
{
result<A> operator()() const noexcept
{
return A::A_ctor();
}
};
Now consider the class B that contains the class A. Being A ctor protected, the following declaration is not valid:
class B {
A a_;
...
};
Maybe the following could work:
class B {
result<A> a_;
constexpr B() noexcept : a_( make<A>{}() ) {}
public:
static result<B> B_ctor() noexcept
{
// Phase 1 ctor
B ret;
// Phase 2 ctor
if ( ret.value().a_.has_failure() ) return MyErrorCode::Error;
if ( /*something else goes wrong*/ ) return MyErrorCode::AnotherError;
return { std::move( ret ) };
}
// ...
void b_method() noexcept
{
a_.value().a_method(); // <-- ugly!
// ...
}
};
but using result<A>
as type for a_
is not very nice. It requires to use a_.value()
everywhere in the code where a_
is used. Moreover if a_
is often used, efficiency could be reduced. Is there any other solution?
There's another dark point with derived classes.
class C : public A {
constexpr C() noexcept : A() { /*...*/ }
public:
// ...
static result<C> C_ctor() noexcept
{
// Phase 1 ctor
C ret;
// Phase 2 ctor
// How to reuse A ctor???
// ...
return { std::move( ret ) };
}
};
In C_ctor
I would like to construct the class starting from A_ctor
to avoid code duplication, something like:
result<C> ret = C::A_ctor();
but there's no available conversion. Any idea to solve this point?
Being A ctor protected, the following declaration is not valid.
You cannot indeed use non-accessible constructors, but move constructor is public,
so you might write your X_ctor
differently:
B(A&& a) noexcept : a_(std::move(a)) {} // use public A(A&&)
static result<B> B_ctor() noexcept
{
result<A> a = make<A>();
if ( a.has_failure() ) return MyErrorCode::Error;
// Phase 1 ctor
B ret(std::move(a.value()));
// Phase 2 ctor
if ( /*something else goes wrong*/ ) return MyErrorCode::AnotherError;
return { std::move( ret ) };
}
In C_ctor I would like to construct the class starting from A_ctor to avoid code duplication
You might have init
functions:
result<bool> init() noexcept
{
// ...
if ( /*something goes wrong*/ ) return MyErrorCode::Error;
return {true};
}
static result<A> A_ctor() noexcept
{
// Phase 1 ctor
A ret;
// Phase 2 ctor
result<bool> a_init = ret.init();
if ( a_init.has_failure() ) return a_init.error();
return { std::move( ret ) };
}
and
result<bool> init() noexcept
{
result<bool> a_init = A::init();
if ( a_init.has_failure() ) return a_init.error();
// ...
if ( /*something goes wrong*/ ) return MyErrorCode::Error;
return {true};
}
static result<C> C_ctor() noexcept
{
// Phase 1 ctor
C ret;
// Phase 2 ctor
result<bool> c_init = ret.init();
if ( c_init.has_failure() ) return c_init.error();
return { std::move( ret ) };
}