Here's a fun challenge.
I wanted to created some boilerplate that allows the clean handling of precondition failures using syntax similar to perl's x or die("reason") approach.
I came up with this deleter:
struct do_exit {
[[noreturn]]
void operator()(void*) const noexcept {
std::exit(code_);
}
int code_;
};
Which we can use to manage the "deletion" of a temporary std::unique_ptr which is maybe pointing at std::cerr:
struct exit_stream
{
exit_stream(int code, std::ostream& os)
: stream_(std::addressof(os), do_exit { code })
{}
std::ostream& as_stream() const { return *stream_; }
operator bool() const { return bool(*stream_); }
template<class T>
friend auto operator<<(const exit_stream& es, T const& t) -> exit_stream const&
{
es.as_stream() << t;
return es;
}
std::unique_ptr<std::ostream, do_exit> stream_;
};
Created with the following function:
exit_stream die(int code = 100)
{
return exit_stream(code, std::cerr);
}
Now we can write the following program:
int main(int argc, char* argv[]) {
// preconditions
argc >= 2 or die(4) << "usage: myprog <inputfile>\n";
auto input = std::ifstream(argv[1]);
input or die(100) << "failed to open " << argv[1] << "\n";
}
This works because when the exit_stream is returned as a temporary, it must be moved (unique_ptr deletes the copy operator). unique_ptr guarantees that the moved-from pointer will be null. It also guarantees that the deleter will only be called if the unique_ptr is not null. Therefore, exit will only be called after all streaming operations have been executed at the call site.
However, the obvious failing is that I can't write:
auto input = std::ifstream(argv[1]]) or die() << "failed to open " << argv[1] << "\n";
Because if I did, input would be a bool.
Here's my question. Can you think of a way to produce the required effect, or as close to it as possible?
I really want to avoid this, for reasons of efficiency in the passing case, and for clarity and elegance:
auto input = (std::ifstream(argv[1]]) or die() << "failed to open " << argv[1] << "\n").get();
Full code:
#include <fstream>
#include <iostream>
#include <cstdlib>
struct do_exit {
[[noreturn]]
void operator()(void*) const noexcept {
std::exit(code_);
}
int code_;
};
struct exit_stream
{
exit_stream(int code, std::ostream& os)
: stream_(std::addressof(os), do_exit { code })
{}
std::ostream& as_stream() const { return *stream_; }
operator bool() const { return bool(*stream_); }
template<class T>
friend auto operator<<(const exit_stream& es, T const& t) -> exit_stream const&
{
es.as_stream() << t;
return es;
}
std::unique_ptr<std::ostream, do_exit> stream_;
};
exit_stream die(int code = 100)
{
return exit_stream(code, std::cerr);
}
int main(int argc, char* argv[]) {
// preconditions
argc >= 2 or die(4) << "usage: myprog <inputfile>\n";
auto input = std::ifstream(argv[1]);
input or die(100) << "failed to open " << argv[1];
}
||?operator||is that the objects must exists, because short-circuiting won't work. So the whole design gets broken. The most problematic part seems thatoperator<<will get evaluated. And it's quite hard to defer it, if possible at all.operator<<'s arguments in a field ofexit_streaminsteaf of immediate printing.||will get rid of the short circuiting behaviour.