自动包装语句持续时间的值

Automatically wrapping values for the duration of a statement

本文关键字:持续时间 语句 包装      更新时间:2023-10-16

是否可以自动将一个值包装在一个其生存期扩展到整个语句的临时中?


起初,我希望在写问题的细节时,能找到一个解决方案或替代方案,但不幸的是,这并没有发生,所以…

我有一个抽象基类Logger,它提供了一个类似流的接口来生成日志语句。给定此类的实例logger,我希望以下内容成为可能:

logger << "Option " << variable << " is " << 42;

与常规流不同,常规流只是从所有组件(上例中为4个组件)生成一个字符串,我希望生成一个类Statement的实例,该实例管理所有语句组件的链表。然后,整个语句通过纯虚拟方法传递给从Logger派生的类,该类可以迭代语句的所有组件,并对它们执行任何操作,包括获取关于它们的类型的信息、检索它们的值或将它们转换为字符串。

棘手的一点是:我想在没有动态内存分配的情况下完成上述操作。这意味着语句的每个组件都必须由一个临时类型包装,该临时类型将组件链接到语句范围内的可遍历列表中!

我在ideone上发布了一个工作示例,但有一个问题:为了生成临时类型的实例,每个组件都需要用函数调用包装。因此,日志语句最终看起来是这样的:

logger << wrap("Option ") << wrap(variable) << wrap(" is ") << wrap(42);

到目前为止,我所有试图摆脱wrap函数的尝试(例如,为组件使用隐式转换构造函数)都失败了,因此出现了这个问题。


如何在不需要显式调用包装函数的情况下,将日志语句的组件自动包装为其组件类型(例如,使用组件的转换构造函数)?

或者,我希望您能为实现相同效果的其他方法提供建议,即允许在从logger派生的类中对log语句的组件进行迭代,而无需动态内存分配。


参考:完整的视频编码:

#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);
}

我有一些疯狂但有效的解决方案。

有了这样的组件实现,你将摆脱整个代码中的模板:

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();
        };
    }
};

其中(1)是知道该做什么的函数。该成员std::function是用于优化的空间。

其余的代码应该看起来像:

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;
}

这将打印:

42
字符串为42
选项字符串为42


UPD正如dyp建议的那样,这里是Component结构的替代实现,没有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();
    }
};

我提出了一个基于元组的解决方案:

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};
}

演示

用途:

int main() {
    Logger log;
    std::string variable = "string";
    log << variable << 42 << "hellon";
}