I am creating custom exception class called app_exception
which is derived from runtime_exception
. I want to put multiple arguments in the constructor, but I can't figure out why the code will not compile. I normally use va_start
with ...
, but I'm trying to do this with Parameter Pack.
template <class Base, class... Args>
class app_error final : public std::runtime_error
{
auto init_base(Args... args)
{
return std::to_string(args);
}
auto init_base(Base msg, Args... args)
{
static std::ostringstream stream;
stream << msg;
stream << init_base(std::forward<Args>(args)...);
return stream.str().c_str();
}
public:
using base = std::runtime_error;
app_error(Base msg, Args... args) : base(init_base(msg, args...)) {}
};
I think this is something along the lines, but I'm not really sure. I want to use it like this:
throw app_error{"FAILED: Exception code is ", exceptionInteger, ". Unable to create ", 5, " needed resources."};
The problem is in the first init_base()
where you write
return std::to_string(args);
without expanding args...
.
But you can't expand args...
because std::to_string()
accept only one parameter.
I suppose you could rename the first init_base()
with a different name (by example, conv()
), to avoid confusion with the other version, and modify it in a template method to convert to string a single argument
template <typename T>
auto conv (T const & arg)
{ return std::to_string(arg); }
then you could use template folding in init_base()
to call conv()
with all arguments, adding the result in the stream
((stream << conv(args)), ...);
But why do you want convert to string? An output stream can accept all types that std::to_string()
accept.
So you can avoid conv()
at all and simply write
((stream << args), ...);
Off Topic: avoid perfect forwarding when you don't have a forwarding reference (as in this case)
Suggestion: transform app_err
in a no-template class, make template the constructor and use perfect forwarding as follows [edit: with a correction from rafix07; thanks]
class app_error final : public std::runtime_error
{
private:
template <typename... Args>
auto init_base (Args && ... args)
{
static std::ostringstream stream;
((stream << std::forward<Args>(args)), ...);
return stream.str();
}
public:
template <typename ... Args>
app_error (Args && ... args)
: std::runtime_error{init_base(std::forward<Args>(args)...)}
{ }
};