Search code examples
c++templatesc++17variadic-templatesfold-expression

I'm trying to create an exception class that uses a parameter pack constructor in c++


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."};

Solution

  • 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)...)}
           { }
     };