В C++ пытаются написать новую строку после каждого оператора перегрузки каскадом
Это немного крепкий орешек. Я пишу функцию ведения журнала, которая пытается выглядеть как std::cout
Таким образом, в основном цель состоит в том, чтобы следующий код делался так, как он
log << "text"; // This would output "text\n";
log << "some " << "text"; //This would output "some text\n"
log << "some number "<< 5; //This would output "some number 5\n"
Я пытался использовать вариадические шаблоны, но далеко не ушел. Вот самая успешная попытка на данный момент:
#include <iostream>
// stream_newliner works by just taking the address of an ostream you
// give it, and forwarding all writes (via operator<<) to that ostream.
// Then, when it finally destructs, it writes the newline.
template <typename CharT, typename Traits>
class stream_newliner
{
public:
// The constructor just takes the address of the stream you give it.
explicit stream_newliner(std::basic_ostream<CharT, Traits>& s) :
p_s_{&s}
{}
// Boilerplate move construction. Nothing special here.
stream_newliner(stream_newliner&& s) : p_s_{nullptr}
{
*this = std::move(s);
}
// On destruction, write the newline. (Note we check the pointer for
// null for technical reasons.)
~stream_newliner()
{
if (p_s_)
{
// If you have std::unhandled_exceptions(), you can check to
// see if a new exception is in flight, and only do the
// newline if there's not.
// Note that I'm writing an X and not a newline so you can
// see it easier.
(*p_s_) << 'X';
}
}
// This is where the magic happens. Any time you do "x << y" where
// x is this type, it just passes it on to the underlying stream.
// When you do "x << y1 << y2", it all works magically because it
// translates to "operator<<(operator<<(x, y1), y2)"... and this
// function just passes each call to the underlying stream.
template <typename T>
auto operator<<(T&& t) -> stream_newliner<CharT, Traits>&
{
(*p_s_) << std::forward<T>(t);
return *this;
}
// Boilerplate move assignment. Nothing special here.
auto operator=(stream_newliner&& s) -> stream_newliner&
{
std::swap(p_s_, s.p_s_);
return *this;
}
// Technically don't need these, but it doesn't hurt to be explicit.
stream_newliner(stream_newliner const&) = delete;
auto operator=(stream_newliner const&) = delete;
private:
// You could use a reference to the stream rather than a pointer,
// but references are harder to work with as class members.
std::basic_ostream<CharT, Traits>* p_s_ = nullptr;
};
// Helper function to make it easier to create.
template <typename CharT, typename Traits>
auto line(std::basic_ostream<CharT, Traits>& s)
{
return stream_newliner<CharT, Traits>{s};
}
auto main() -> int
{
line(std::cout) << "foo";
std::cout << '\n';
line(std::cout) << 1 << ' ' << 2 << ' ' << 3;
std::cout << '\n';
}
2 ответа
Я думаю, что это не слишком сложно. Посмотрим:
struct eol_log
{
eol_log(std::ostream& os) : os_(os) {}
~eol_log() { os_ << '\n'; }
template <typename T>
std::ostream& operator<<(const T& t) && { return os_ << t; }
std::ostream& os_;
};
Использование:
eol_log(std::cout) << "Foo" << "Bar" << 10;
eol_log(std::cout) << 10;
Временный объект действует как своего рода "охрана полного выражения".
Если вы предпочитаете иметь постоянный объект, который действует как ваш приемник журналов, то вы можете обернуть вышеупомянутое в класс, но теперь вам нужно переместить состояние быть последним элементом в цепочке:
class EolLogger
{
class RvalLog
{
friend class EolLogger;
RvalLog(std::ostream* os) : os_(os) {}
RvalLog(RvalLog&& rhs) : os_(rhs.os_) { rhs.os_ = nullptr; }
std::ostream* os_;
public:
template <typename T>
RvalLog operator<<(const T& t) && { *os_ << t; return std::move(*this); }
~RvalLog() { if (os_) *os_ << '\n'; }
};
std::ostream& os_;
public:
EolLogger(std::ostream& os) : os_(os) {}
template <typename T>
RvalLog operator<<(const T& t) { return RvalLog(&os_) << t; }
};
Использование:
EolLogger elog(std::cout);
elog << "Foo";
elog << 1 << 2 << 3;
Кто-то опубликовал это, и это делает свою работу.
#include <iostream>
using namespace std;
struct Liner {
bool Owned = true;
Liner() = default;
Liner(Liner &&O) { O.Owned = false; }
~Liner() { if (Owned) std::cout << '\n'; }
};
template <typename T>
const Liner &operator<<(const Liner &L, T &&E) {
std::cout << std::forward<T>(E);
return L;
}
struct SubLiner {
operator Liner() const { return {}; }
};
const static SubLiner line;
int main() {
line << "hello " << "world!";
line << 1 << 2;
line << 3;
return 0;
}