2 minute read

I was looking to validate and throw on invalid arguments in my code. This meant I had to write something like.

void foo(size_t size) {
  if (size > this.current_size) {
    throw std::invalid_argument("Size cannot be greater than current size.");
  }
  ...
}

Not so bad really but I also wanted to create a dynamically created message.

void foo(size_t size)
  if (size > this.current_size) {
    std::stringstream text;
    text << "Size " << size << " cannot be greater than current size of " << this.current_size << ".";
    throw std::invalid_argument(text.str());
  }
  ...
}

We could shorten it…

void validate(bool condition) {
  if (condition) return;
  std::stringstream text;
  text << "Size " << size << " cannot be greater than current size of " << this.current_size << ".";
  throw std::invalid_argument(text.str());
}
void foo(size_t size)
  validate(size > this.current_size);
  ...
}

… but it isn’t very re-usable since the message is hardcoded.

C++20 offers the ability to use std::format.

void foo(size_t size)
  if (size > this.current_size) {
    throw std::invalid_argument(std::format("Size {} cannot be greater than current size of {}.", size, this.current_size));
  }
  ...
}

Pretty nice, although for people not on that standard they are left with the previous validation approach. There is a different way to do it however.

void foo(size_t size)
  invariant::eval(size > this.current_size) << 
    "Size " << size << " cannot be greater than current size of " << this.current_size << "."; 
  ...
}

Here is how it is implemented.

class invariant
{
 private:
  const bool condition_;
  std::stringstream message_;

  invariant(bool condition) : condition_(condition), message_() {}  
  invariant() = delete;
  invariant(const invariant &) = default;

 public:
  ~invariant() noexcept(false)
  {
    if (!condition_)
    {
      // Living dangerously, guaranteed not to live on the stack though.
      throw std::invalid_argument(message_.str());
    }
  }

  template <typename Text>
  friend invariant &&operator<<(invariant &&item, const Text &text)
  {
    item.message_ << text;
    return std::move(item);
  }

  static invariant eval(bool condition)
  {
    return invariant(condition, "");
  }
};

Not so bad maybe for pre C++20 but why have a throw in the destructor? This can be dangerous since it is called when the stack unwinds.

  ~invariant() noexcept(false)
  {
    if (!condition_)
    {
      // Living dangerously, guaranteed not to live on the stack though.
      throw std::invalid_argument(message_.str());
    }
  }

The key is to make sure it never gets on the stack in the first place. This means making it never becomes an lvalue. Herb Sutter discusses this strategy by ensuring that the object only ever can be an rvalue reference than cannot be copied or constructed. Here are the pieces that make this possible.

  invariant(bool condition) : condition_(condition), message_() {}  
  invariant() = delete;
  invariant(const invariant &) = default;

Lastly is getting the dynamic message input.

  template <typename Text>
  friend invariant &&operator<<(invariant &&item, const Text &text)
  {
    item.message_ << text;
    return std::move(item);
  }

Note how an rvalue reference is passed in and out. This ensures that it is never copied while also modifying the object’s message state.

This exercise was fun and informative for myself but can be quite dangerous if not understood well and modified without such knowledge. There is also a safer alternative (well why didn’t you say so?!) using a variadic template via parameter pack. You can find the answers to string concatenation on this StackOverflow post. An adaptation from the top answer would look like.

template< typename ...Args>
void invariant(bool condition, const Args&... args)
{
  if (condition) return;
  std::stringstream message;
  using List= int[];
  (void)List{0, ((void)(message << args), 0 ) ...};
  throw std::invalid_argument(message.str());   
}

Tags:

Updated:

Leave a comment