1

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."};
max66
  • 65,235
  • 10
  • 71
  • 111
Arush Agarampur
  • 1,340
  • 7
  • 20

1 Answers1

2

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)...)}
       { }
 };
max66
  • 65,235
  • 10
  • 71
  • 111
  • Hey, thanks this works perfectly. So I can learn, can you explain the syntax of `((stream << conv(args)), ...);`? – Arush Agarampur Dec 08 '19 at 20:23
  • @ArushAgarampur - is "folding" or "template folding" or "[fold expression](https://en.cppreference.com/w/cpp/language/fold)". Is available starting from C++17 but I suppose you're using C++17 because, in your example, you're using implicit deduction guides for `app_error`. If you can use only C++11 or C++14, I can transform the code for older versions. – max66 Dec 08 '19 at 20:27
  • 1
    @rafix07 - Good point! I didn't noticed it. Corrected. Thanks. – max66 Dec 08 '19 at 20:31
  • 1
    Yes, I am using c++ 17, i just had not seen that before. Thanks for the explanation and for the edit that @rafix07 suggested. – Arush Agarampur Dec 08 '19 at 20:33
  • @ArushAgarampur - yes: very good point the rafix07's observation. – max66 Dec 08 '19 at 20:38