I'm designing a logger library in C++, and I've been stuck with my implementation of a formatter class.
My goal is to be able to create a message "formatter", which will take a string (something like "[A] [B]", or "[B]") and decide which policies to attach to my formatter.
Here is a simplified version of my policy implementation:
#include <iostream>
#include <string>
#include <memory>
template <typename PolicyA, typename PolicyB>
class Formatter : private PolicyA, private PolicyB {
public:
void Format(std::string& param_message) const {
PolicyA::A(param_message);
PolicyB::B(param_message);
}
};
class Default {
protected:
virtual void A(std::string& param_message) const = 0;
};
class ADefault : public virtual Default {
protected:
void A(std::string& param_message) const {}
};
class AExplicit : public virtual Default {
protected:
void A(std::string& param_message) const { param_message = "[A] " + param_message; }
};
class BDefault : public virtual Default {
protected:
void B(std::string& param_message) const {}
};
class BExplicit : public virtual Default {
protected:
void B(std::string& param_message) const { param_message = "[B] " + param_message; }
};
int main() {
std::string message_1 = "message_1";
std::string message_2 = "message_2";
Formatter<ADefault, BDefault> default_message;
Formatter<AExplicit, BExplicit> explicit_message;
default_message.Format(message_1);
explicit_message.Format(message_2);
std::cout << message_1 << std::endl;
std::cout << message_2 << std::endl;
}
The policy works as I intended, but how would I go about creating a sort of Builder function? One that could return a Fromatter from a set of parameters. I already have an implementation of the string interpreter, which returns an enum buffer of formats:
enum class Format : uint16_t {
NONE = 0x000,
A = 0x001,
B = 0x002,
};
Format ConfigFormatter(std::string& param_configuration) {
Format formatter_configuration = Format::NONE;
if (param_configuration.find("[A]") != std::string::npos) {
formatter_configuration += Format::A;
}
if (param_configuration.find("[B]") != std::string::npos) {
formatter_configuration += Format::B;
}
return formatter_configuration;
}
Is it possible to store the created Formatter as a member variable of another class? And should the Builder class be a variadic class with specializations for every type? I've looked for on variadic factories, but could not find anything that could help me.
Maybe this could help:
#include <iostream>
#include <sstream>
#include <string>
#include <string_view>
enum class LogLevel { Debug, Info, Warn, Error, Fatal, Off };
template <typename... Fmts>
struct Logger {
template <LogLevel Level>
inline void Log(std::string_view msg) {
if constexpr (Level == LogLevel::Off) {
return;
}
std::stringstream out{};
((Fmts::template Format<Level>(out)), ...);
std::clog << out.str() << msg << std::endl;
}
};
template <typename... Fmts>
struct LoggerBuilder {
template <typename Fmt>
inline auto With() {
return LoggerBuilder<Fmts..., Fmt>{};
}
inline auto Build() {
return Logger<Fmts...>{};
}
};
struct DefaultFormatter {
template <LogLevel Level>
static inline void Format(std::stringstream& buf) {
}
};
struct ExplicitFormatter {
template <LogLevel Level>
static inline void Format(std::stringstream& buf) {
using enum LogLevel;
if constexpr (Level == Debug) {
buf << "[DEBUG] ";
} else if constexpr (Level == Info) {
buf << "[INFO] ";
} else if constexpr (Level == Warn) {
buf << "[WARN] ";
} else if constexpr (Level == Error) {
buf << "[ERROR] ";
} else if constexpr (Level == Fatal) {
buf << "[FATAL] ";
}
}
};
struct LongTimeFormatter {
template <LogLevel Level>
static inline void Format(std::stringstream& buf) {
buf << "[00:00:00.00 00/00/0000] ";
}
};
int main() {
auto logger = LoggerBuilder{}
.With<LongTimeFormatter>()
.With<ExplicitFormatter>()
.Build();
logger.Log<LogLevel::Debug>("My Debug Message");
logger.Log<LogLevel::Info>("My Info Message");
logger.Log<LogLevel::Warn>("My Warn Message");
logger.Log<LogLevel::Error>("My Error Message");
logger.Log<LogLevel::Fatal>("My Fatal Message");
logger.Log<LogLevel::Off>("Not Printed");
return 0;
}
It uses templates rather than RTTI, so you get almost a zero-cost abstraction.
Related
I'm a newer of using C++ template and I got trouble with template compiling.
I want to write a similar factory method with template but compiler error says that 'ip is not the member of _FileWriterInfo'. I was confused because it has be defined in NetWriterInfo struct but not in FileWriterInfo. And if I cancel the 'ip' member defination, compiler works. Apparently T param of NetWriter may infer to FileWriterInfo struct by mistake. How can I get rid of it? plz help.
#include <iostream>
#include <string>
enum WriterFormat
{
WTYPE_FILE = 0,
WTYPE_NET = 1
};
typedef struct _FileWriterInfo
{
std::string name;
std::string page;
}FileWriterInfo;
typedef struct _NetWriterInfo
{
std::string name;
std::string ip;
}NetWriterInfo;
template<typename T>
class Writer
{
public:
virtual ~Writer() {}
virtual std::string Write(T info) = 0;
};
template<typename T>
class FileWriter : public Writer<T>
{
public:
std::string Write(T info) override {
std::cout << "name:" << info.name << "\n";
std::cout << "page:" << info.page << "\n";
return info.name;
}
};
template<typename T>
class NetWriter : public Writer<T>
{
public:
std::string Write(T info) override {
std::cout << "name:" << info.name << "\n";
std::cout << "ip:" << info.ip << "\n";
return info.name;
}
};
class Creator
{
Creator() {};
public:
template<typename T>
static Writer<T>* CreateWriter(WriterFormat fmt)
{
Writer<T>* p = nullptr;
if (fmt == WTYPE_FILE)
p = new FileWriter<T>;
if (fmt == WTYPE_NET)
p = new NetWriter<T>;
return p;
}
};
void WriteFile()
{
FileWriterInfo info = { "Hello","100" };
Writer<FileWriterInfo>* w = Creator::CreateWriter<FileWriterInfo>(WTYPE_FILE);
w->Write(info);
return;
}
int main()
{
WriteFile();
return 0;
}
The CreateWriter function instantiates the FileWriter and NetWriter classes with the FileWriterInfo structure. Accordingly, the compiler tries to instantiate the NetWriter::Write function with the type FileWriterInfo, and we get an error.
You can place Write methods directly to FileWriterInfo and NetWriterInfo stuctures (according to the principle of data encapsulation). It also can simplify the code.
my header code:
template <typename T>
class A
{
}
template<> class A<short>;
template<> class A<float>;
in my cpp, i want to use a map to contain different type a, like following code:
class B
{
map<int, A*> a; /* how to declare a */
public:
AddA(int key, int type)
{
if (type == 1)
{
a.insert({ key, new A<short>() });
}
else
{
a.insert({ key, new A<float>() });
}
}
template<typename T>
func(int key, T v)
{
a[key].func(v);
}
};
question: how to implement it?
edit # 0410, here is my solution:
class ABase
{
virtual void func(void* t)=0;
}
template <typename T> A;
template <short> A : public ABase
{
void func(void* t) override
{
auto value = *static_cast<short*>(t);
// do processing
}
template <float> A : public ABase
{
void func(void* t) override
{
auto value = *static_cast<float*>(t);
// do processing
}
CPP: used a map of ABase* for all the template class, and use a virtual func for all template interface
main()
{
map<int, ABase*> objs;
objs.insert({0, new A<short>()});
objs.insert({1, new A<float>()});
auto value=0;
objs[0]->func(&value);
auto value1=0.f;
objs[1]->func(&value1);
}
If you really need to have multiple types in a single map, you can use a map of std::variant. But as already mentioned in the comments, this might be a design problem.
But if you need it, you can proceed with the std::map< int, std::variant<>>. Later on, if you want to access the stored element, you have to call std::visit to pick the element which is stored in std::variant.
See the following example:
template < typename T >
struct A
{
};
// spezialize if needed, here only for demonstration purpose
template <> struct A<short> { void func(short parm) { std::cout << "A<short> with " << parm << std::endl; } };
template <> struct A<float> { void func(float parm) { std::cout << "A<float> with " << parm << std::endl; } };
class B
{
std::map<int, std::variant<A<short>*, A<float>*>> a;
public:
void AddA(int key, int type)
{
if (type == 1)
{
a.insert({ key, new A<short>() });
}
else
{
a.insert({ key, new A<float>() });
}
}
template<typename T>
void func(int key, T v)
{
std::visit( [&v]( auto ptr ) { ptr->func(v); }, a[key] );
}
};
int main()
{
B b;
b.AddA( 1, 1 );
b.AddA( 2, 2 );
b.func( 1, 99 );
b.func( 2, 100 );
}
You can't achieve the problem with templates. Template declaration is only a blueprint for a type candidate.
"A<short>" is the type not "A" itself.
You can achieve your problem through inheritance.
Edit: Code is updated according to #MSalters' comment. Thanks.
#include <iostream>
#include <map>
class A
{
public:
virtual void func(void* x) = 0;
};
class A_Short : public A
{
public:
void func(void* x)
{
short* value = static_cast<short*>(x);
std::cout << "short value: " << *value << std::endl;
}
};
class A_Float : public A
{
public:
void func(void* x)
{
float* value = static_cast<float*>(x);
std::cout << "float value: " << *value << std::endl;
}
};
template<typename T>
class A_Derived : public A
{
public:
void func(void* x)
{
T* value = static_cast<T*>(x);
std::cout << "[Derived] value: " << *value << std::endl;
}
};
class B
{
std::map<int, A*> a; /* how to declare a */
public:
void AddA(int key, int type)
{
if (type == 1)
{
a.insert({ key, new A_Short() });
}
else if(type == 2)
{
a.insert({key, new A_Derived<short>()});
}
else if(type == 3)
{
a.insert({key, new A_Derived<float>()});
}
else
{
a.insert({ key, new A_Float() });
}
}
// Assumes that user knows to use which T for any "key"
template<typename T>
void func(int key, T v)
{
a[key]->func(v);
}
};
int main()
{
B b;
b.AddA(1, 1);
b.AddA(2, 8);
b.AddA(3, 2);
b.AddA(4, 3);
short s = 1;
float f = 7.1;
short s2 = 2;
float f2 = 7.2;
b.func(1, &s);
b.func(2, &f);
b.func(3, &s2);
b.func(4, &f2);
}
I read this article about C++ factory class with a self registering capability of the concrete classes. Really like it, expecially the demangled name solution used as a key for the registered classes.
There is one main issue I would like to solve: how can we modify the factory to be able to support concrete classes with different constructor parameters?
// Dog and Cat both derive from Animal, but have different constructor parameters
class Dog : public Animal::Registrar<Dog> {
public:
Dog(int param1, std::string param2) : m_x(param1) {}
void makeNoise() { std::cerr << "Dog: " << m_x << "\n"; }
private:
int m_x;
};
class Cat : public Animal::Registrar<Cat> {
public:
Cat(bool param1) : m_flag(param1) {}
void makeNoise() { std::cerr << "Cat: " << m_x << "\n"; }
private:
bool m_flag;
};
I was thinking that maybe the parameter pack of template <class Base, class... Args> class Factory should be moved to the template <class T> struct Registrar, but I can't found a proper solution.
Full original code below
#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
#include <cstdlib>
#include <cxxabi.h>
std::string demangle(const char *name) {
int status = -4; // some arbitrary value to eliminate the compiler warning
std::unique_ptr<char, void (*)(void *)> res{
abi::__cxa_demangle(name, NULL, NULL, &status), std::free};
return (status == 0) ? res.get() : name;
}
template <class Base, class... Args> class Factory {
public:
template <class ... T>
static std::unique_ptr<Base> make(const std::string &s, T&&... args) {
return data().at(s)(std::forward<T>(args)...);
}
template <class T> struct Registrar : Base {
friend T;
static bool registerT() {
const auto name = demangle(typeid(T).name());
Factory::data()[name] = [](Args... args) -> std::unique_ptr<Base> {
return std::make_unique<T>(std::forward<Args>(args)...);
};
return true;
}
static bool registered;
private:
Registrar() : Base(Key{}) { (void)registered; }
};
friend Base;
private:
class Key {
Key(){};
template <class T> friend struct Registrar;
};
using FuncType = std::unique_ptr<Base> (*)(Args...);
Factory() = default;
static auto &data() {
static std::unordered_map<std::string, FuncType> s;
return s;
}
};
template <class Base, class... Args>
template <class T>
bool Factory<Base, Args...>::Registrar<T>::registered =
Factory<Base, Args...>::Registrar<T>::registerT();
struct Animal : Factory<Animal, int> {
Animal(Key) {}
virtual void makeNoise() = 0;
};
class Dog : public Animal::Registrar<Dog> {
public:
Dog(int x) : m_x(x) {}
void makeNoise() { std::cerr << "Dog: " << m_x << "\n"; }
private:
int m_x;
};
class Cat : public Animal::Registrar<Cat> {
public:
Cat(int x) : m_x(x) {}
void makeNoise() { std::cerr << "Cat: " << m_x << "\n"; }
private:
int m_x;
};
// Won't compile because of the private CRTP constructor
// class Spider : public Animal::Registrar<Cat> {
// public:
// Spider(int x) : m_x(x) {}
// void makeNoise() { std::cerr << "Spider: " << m_x << "\n"; }
// private:
// int m_x;
// };
// Won't compile because of the pass key idiom
// class Zob : public Animal {
// public:
// Zob(int x) : Animal({}), m_x(x) {}
// void makeNoise() { std::cerr << "Zob: " << m_x << "\n"; }
// std::unique_ptr<Animal> clone() const { return
// std::make_unique<Zob>(*this); }
// private:
// int m_x;
// };
// An example that shows that rvalues are handled correctly, and
// that this all works with move only types
struct Creature : Factory<Creature, std::unique_ptr<int>> {
Creature(Key) {}
virtual void makeNoise() = 0;
};
class Ghost : public Creature::Registrar<Ghost> {
public:
Ghost(std::unique_ptr<int>&& x) : m_x(*x) {}
void makeNoise() { std::cerr << "Ghost: " << m_x << "\n"; }
private:
int m_x;
};
int main() {
auto x = Animal::make("Dog", 3);
auto y = Animal::make("Cat", 2);
x->makeNoise();
y->makeNoise();
auto z = Creature::make("Ghost", std::make_unique<int>(4));
z->makeNoise();
return 0;
}
Here is simplified sample of problem, featuring CRTP:
#include <type_traits>
#include <iostream>
enum ActionTypes {
eInit = 2 << 0,
eUpdate = 2 << 1,
eMultUpdate = 2 << 2
};
template <class Data,
unsigned Actions = eInit|eUpdate|eMultUpdate>
class ActionData
{
template<ActionTypes As /*???*/>
struct action {
static void exec(Data*) { std::cout << "ActionData:: /*dummy*/ exec()\n"; };
static void exec(Data*,int) { std::cout << "ActionData::/*dummy*/ exec(int)\n"; };
};
template<>
struct action < /*???*/ >
{
static void exec(Data*) { /*...*/ };
};
template<>
struct action < /*???*/ >
{
static void exec(Data*, int) { /*...*/ };
};
Data* derived() { return static_cast<Data*>(this); }
protected:
void init() { action<eInit>::exec(derived()); }
void update() { action<eUpdate>::exec(derived()); }
void update(int key) { action<eMultUpdate>::exec(derived()); }
public:
enum Keys { DEFAULT_KEY = -1 };
void call(ActionTypes a, int key = DEFAULT_KEY)
{
switch (a) {
case eInit:
init(); break;
case eUpdate:
if (key == DEFAULT_KEY)
update();
else
case eMultUpdate:
update(key);
}
}
};
class Test : public ActionData<Test, eUpdate>
{
public:
void update() { std::cout << "Test :: update()\n"; }
};
int main()
{
Test actor;
ActionTypes a = eInit;
actor.call(a, 0); // useless here but must be possible.
actor.call(eUpdate, 0);
actor.call(eUpdate);
}
Essentially not all derived classes may implement all handlers, a enum is used to declare that and a dummy version of handler must be called. The problem is that it's not possible to select any implementation but default one using enum and enable_if alone, it requires a non-type parameter, which stupefied me.
PS. Another problem is target platform is limited to C++98\C++03 or tr1 C++11 (no variadic templates). The awkward interface is a legacy of dynamic (but not used as such) polymorphic architecture using function pointers in a big C (not C++!) project. Necessity of pointers or vtable made system unstable to programmer errors leading to vtable being overwritten.
I didn't realize that I should use a partial specialization for all cases including where there is no match:
#include <type_traits>
#include <iostream>
enum ActionTypes {
eInit = 2 << 0,
eUpdate = 2 << 1,
eMultUpdate = 2 << 2
};
template <class Data,
unsigned Actions = eInit|eUpdate|eMultUpdate>
class ActionData
{
// Never gets selected
template<ActionTypes A, typename Enable = void > struct action {};
template< ActionTypes A >
struct action<A, typename std::enable_if<(A & Actions) == 0>::type >
{
static void exec(Data*) { std::cout << "ActionData:: /*dummy*/ exec()\n"; };
static void exec(Data*,int) { std::cout << "ActionData::/*dummy*/ exec(int)\n"; };
};
template< ActionTypes A >
struct action < A, typename std::enable_if<(A & Actions) == eInit>::type >
{
static void exec(Data* o) { o->Data::init(); };
};
template< ActionTypes A >
struct action < A, typename std::enable_if<(A & Actions) == eUpdate>::type >
{
static void exec(Data* o) { o->Data::update(); };
};
template< ActionTypes A >
struct action < A, typename std::enable_if<(A & Actions) == eMultUpdate>::type >
{
static void exec(Data* o, int key) { o->Data::update(key); };
};
Data* derived() { return static_cast<Data*>(this); }
protected:
void init() { action<eInit>::exec(derived()); }
void update() { action<eUpdate>::exec(derived()); }
void update(int key) { action<eMultUpdate>::exec(derived(), key); }
public:
enum Keys { DEFAULT_KEY };
void call(ActionTypes a, int key = DEFAULT_KEY)
{
switch (a) {
case eInit:
init(); break;
case eUpdate:
if (key == DEFAULT_KEY) {
update();
break;
} else {
case eMultUpdate:
update(key);
break;
};
}
}
};
class Test : public ActionData<Test, eUpdate>
{
public:
void update() { std::cout << "Test :: update()\n"; }
};
int main()
{
Test actor;
ActionTypes a = eInit;
actor.call(a, 0);
actor.call(eUpdate);
actor.call(eMultUpdate, 0);
}
Is it possible to automatically wrap a value in a temporary whose lifetime extends across the entire statement?
Originally I hoped a solution or alternative to my problem would present itself while writing the details for the question, unfortunately that didn't happen, so...
I have an abstract base class Logger that provides a streaming-like interface for generating log statements. Given an instance logger of this class, I want the following to be possible:
logger << "Option " << variable << " is " << 42;
Unlike regular streams, which simply generate a string from all the components (4 components in the example above), I want to generate an instance of a class Statement that manages a linked list of all the statement's components. The entire statement is then passed via pure virtual method to a class derived from Logger, which can iterate over all the components of the statement and do whatever with them, including obtaining information about their type, retrieving their value, or converting them to a string.
The tricky bit: I want to do the above without dynamic memory allocations. This means that every component of the statement must be wrapped by a temporary type that links the components into a traversable list, within the scope of the statement!
I posted a working example on ideone, with one problem: every component needs to be wrapped by a function call in order to generate an instance of the temporary type. The log statement therefore ends up looking like this:
logger << wrap("Option ") << wrap(variable) << wrap(" is ") << wrap(42);
All my attempts to get rid of the wrap function (e.g., using an implicit converting constructor for the component), have failed thus far, therefore this question.
How can the components of the log statement automatically be wrapped in their component type (e.g., using a converting constructor for the component), without the need for an explicit call to a wrapping function?
Alternatively, I would appreciate suggestions for other ways that achieve the same effect, i.e., allowing iteration over the components of the log statement in a class derived from logger, without dynamic memory allocations.
Reference: Full code on ideone:
#include <iostream>
#include <sstream>
struct Statement;
struct Logger;
struct ComponentBase;
//------------------------------------------------------------------------------
struct ComponentBase {
mutable ComponentBase *next;
ComponentBase() : next(nullptr) { }
virtual std::string toString() = 0;
};
template <typename T>
struct Component : ComponentBase {
T value;
Component(T value) : value(value) { }
~Component() { }
virtual std::string toString() {
std::stringstream ss;
ss << value;
return ss.str();
}
};
struct ComponentIterator {
ComponentBase *ptr;
ComponentIterator(ComponentBase *ptr) : ptr(ptr) { }
ComponentBase &operator*() { return *ptr; }
void operator++() { ptr = ptr->next; }
bool operator!=(ComponentIterator &other) { return (ptr != other.ptr); }
};
//------------------------------------------------------------------------------
struct Statement {
Logger *logger;
ComponentBase *front;
ComponentBase *back;
ComponentIterator begin() { return front; }
ComponentIterator end() { return nullptr; }
template <typename T>
Statement(Logger &logger, Component<T> &component)
: logger(&logger), front(&component), back(&component) { }
~Statement();
template <typename T>
Statement &operator<<(Component<T> &&component) {
back->next = &component;
back = &component;
return *this;
}
};
//------------------------------------------------------------------------------
struct Logger {
template <typename T>
Statement operator<<(Component<T> &&component) {
return {*this, component};
}
virtual void log(Statement &statement) = 0;
};
Statement::~Statement() {
logger->log(*this);
}
//------------------------------------------------------------------------------
template <typename T>
Component<T const &> wrap(T const &value) {
return value;
}
template <size_t N>
Component<char const *> wrap(char const (&value)[N]) {
return value;
}
//------------------------------------------------------------------------------
struct MyLogger : public Logger {
virtual void log(Statement &statement) override {
for(auto &&component : statement) {
std::cout << component.toString();
}
std::cout << std::endl;
}
};
int main() {
std::string variable = "string";
MyLogger logger;
logger << wrap("Option ") << wrap(variable) << wrap(" is ") << wrap(42);
}
I have some crazy but working solution.
Having component implemented like this you will get rid of templates all over your code:
struct Component
{
mutable Component *next;
typedef std::function<std::string()> ToStringFunction;
ToStringFunction toString; // <-- 1
template<typename T>
Component(const T& value)
: next(nullptr),
toString(nullptr)
{
toString = [&value](){
std::stringstream ss;
ss << value;
return ss.str();
};
}
};
Where (1) is the unction that knows what to do. This member std::function is a space for optimization.
And the rest of the code should look like:
struct ComponentIterator {
Component *ptr;
ComponentIterator(Component *ptr) : ptr(ptr) { }
Component &operator*() { return *ptr; }
void operator++() { ptr = ptr->next; }
bool operator!=(ComponentIterator &other) { return (ptr != other.ptr); }
};
//------------------------------------------------------------------------------
struct Statement {
Logger *logger;
Component *front;
Component *back;
ComponentIterator begin() { return front; }
ComponentIterator end() { return nullptr; }
Statement(Logger &logger, Component &component)
: logger(&logger), front(&component), back(&component) { }
~Statement();
Statement &operator<<(Component &&component) {
back->next = &component;
back = &component;
return *this;
}
};
//------------------------------------------------------------------------------
struct Logger {
Statement operator<<(Component &&component) {
return{ *this, component };
}
virtual void log(Statement &statement) = 0;
};
Statement::~Statement() {
logger->log(*this);
}
//------------------------------------------------------------------------------
struct MyLogger : public Logger {
virtual void log(Statement &statement) override {
for (auto &&component : statement) {
std::cout << component.toString();
}
std::cout << std::endl;
}
};
int main() {
std::string variable = "string";
MyLogger logger;
//logger << wrap("Option ") << wrap(variable) << wrap(" is ") << wrap(42);
logger << 42;
logger << variable << " is " << 42;
logger << "Option " << variable << " is " << 42;
}
this will print:
42
string is 42
Option string is 42
UPD
as dyp advised here is alternative implementation of the Component structure without lambda:
struct Component
{
mutable Component *next;
void* value;
std::string toString(){
return _toString(this);
}
template<typename T>
Component(const T& inValue)
: next(nullptr),
value((void*)&inValue),
_toString(toStringHelper<T>)
{}
private:
typedef std::string(*ToStringFunction)(Component*);
ToStringFunction _toString;
template<typename T>
static std::string toStringHelper(Component* component)
{
const T& value = *(T*)component->value;
std::stringstream ss;
ss << value;
return ss.str();
}
};
I propose a solution tuple based:
template <class... Ts> class streamTuple;
struct Logger {
template <typename T>
streamTuple<T> operator<<(const T& t);
template <typename Tuple, std::size_t ... Is>
void dispatch(const Tuple& tup, std::index_sequence<Is...>)
{
int dummy[] = {0, (void(std::cout << std::get<Is>(tup) << " "), 0)...};
static_cast<void>(dummy); // Avoid unused variable warning
}
// Logger can take generic functor to have specific dispatch
// Or you may reuse your virtual method taking ComponentBase.
};
template <class... Ts> class streamTuple
{
public:
streamTuple(Logger* logger, const std::tuple<Ts...>& tup) :
logger(logger), tup(tup) {}
streamTuple(streamTuple&& rhs) : logger(rhs.logger), tup(std::move(rhs.tup))
{
rhs.logger = nullptr;
}
~streamTuple()
{
if (logger) {
logger->dispatch(tup, std::index_sequence_for<Ts...>());
}
}
template <typename T>
streamTuple<Ts..., const T&> operator << (const T& t) &&
{
auto* moveddLogger = logger;
logger = nullptr;
return {moveddLogger, std::tuple_cat(tup, std::tie(t))};
}
private:
Logger* logger;
std::tuple<Ts...> tup;
};
template <typename T>
streamTuple<T> Logger::operator<<(const T& t) {
return {this, t};
}
Demo
And usage:
int main() {
Logger log;
std::string variable = "string";
log << variable << 42 << "hello\n";
}