Re: Preventing implicit calling of non-explicit constructor.
jason.cipriani@gmail.com wrote:
I have an application with a class "AppException" derived from
std::exception. I have full control over the implementation of
"AppException". I have two constructors like this:
class AppException {
public:
...
AppException (const char *msg, ...);
AppException (const std::exception &cause, const char *msg, ...);
...
};
The first constructor takes a printf format string and optional
parameters. The second takes an std::exception as the root cause, and
the same printf-style message. This functionality is critical (I need
to be able to construct an AppException from just a message, or from a
message and an std::exception root cause), although this particular
interface is not critical.
My problem is that std::exception has a non-explicit const char *
constructor. Therefore it can be implicitly converted from a const
char *. So in cases where I am using the no-cause constructor but
where my format parameters are a single additional string, e.g.:
throw AppException("Some string: %s", someString);
The compiler (VS 2008's compiler) complains that both constructors are
possible matches (the second constructor also matches, it attempts to
implicitly convert the const char * to an std::exception, and pass
someString as "msg").
How can I get rid of this ambiguity, but still keep the same
functionality? I'm kind of frazzled and having trouble coming up with
ideas. If I could somehow say that I wanted std::exception(const char
*) to be explicit, that would be one way to solve the problem, but I
don't think that's possible.
Thanks,
Jason
std::exception doesn't have a constructor that takes a const char *.
It's full definition according to 18.6.1 is:
namespace std {
class exception {
public:
exception() throw();
exception(const exception&) throw();
exception& operator=(const exception&) throw();
virtual ??exception() throw();
virtual const char* what() const throw();
};
}
Seems like you've found an error in Microsoft's implementation.
Anyway, the workaround is to exploit the fact that only one implicit
conversion is allowed. Create a class to wrap a standard exception:
class ExceptionWrapper
{
public:
ExceptionWrapper(const std::exception & e) : m_ref(e)
{}
const std::exception & get() const
{
return m_ref;
}
private:
const std::exception & m_ref;
};
Then change your exception class's interface to accept that:
class AppException {
public:
...
AppException (const char *msg, ...);
AppException (const ExceptionWrapper &cause, const char *msg, ...);
...
};
Within the AppException class use ExceptionWrapper::get to access the
exception.
You can still pass a std::exception as the first argument because an
ExceptionWrapper can be implicitly created, but because only one
implicit conversion is allowed, there is no way for the second
constructor to match a call with const char * as the first argument.
--
Alan Johnson