在类模板中消除基类名称的歧义

disambiguate base class name in class template

本文关键字:基类 歧义      更新时间:2023-10-16

我们有一个日志框架,它允许我们将上下文特定的信息自动添加到日志消息中。

这是通过继承Context类模板实现的,该模板使用CRTP pattern/static polymorphism,在子类上调用Token()函数来获得对象的上下文。

在日志记录时,我们使用扩展为GetLog()的宏。

  • 成员函数在基类中找到GetLog时,它获取记录器并添加上下文Token
  • 当在自由函数Context派生类中,GetLog在全局命名空间中被找到,并且它只获得记录器。

下面是一个完整的工作示例:

我的问题是,如果我有一个类模板,这是反过来衍生的Context,例如:

template<typename T>
struct Foo : Context<Foo<T>>;

当我尝试在成员函数内登录时,我必须用this->前缀LOG,以便使方法GetLog()成为依赖的名称

是否有任何方法可以让类模板成员函数使用LOG而不使用this->并将其解析为Context::GetLog() ?

  • Token设为虚函数,并使用动态多态性获取上下文。缺点是这会在每个日志调用上增加一个v表查找(对于大量Context派生对象,编译器能够内联吗?)我想让它尽可能精简。
  • 使用std::enable_ifstd::is_base_of来区分Contextnon-Context派生对象。我不认为我能让这个和自由函数一起工作?
  • 还有其他办法吗?

下面是一个工作示例:

#include <iostream>
// stub logging object to make the example work - just logs to stdout
struct Log
{
    template<typename T>
    friend Log& operator<<(Log& l, T d)
    {
        std::cout << d;
        return l;
    }
    friend Log& operator<<(Log& l, std::ostream& (*f)(std::ostream&))
    {
        std::cout << f;
        return l;
    }
};
Log gLog;
#define LOG GetLog()
// GetLog in the global namespace for non-Context derived classes, free functions etc
Log& GetLog()
{
    return gLog;
}
// classes derive from this to add context specific information when logging
template<typename Self>
struct Context
{
    // this GetLog adds prefix to Context derived classes
    Log& GetLog()
    {
        static_cast<const Self*>(this)->Token(gLog); // add the Context's Token to the log
        return gLog << ": ";
    }
};
//-------------------------
template<typename T>
struct Foo : Context<Foo<T>>
{
    void Func1()
    {
        LOG << __func__ << std::endl;       // resolves to the global GetLog() free-function
    }
    void Func2()
    {
        this->LOG << __func__ << std::endl; // notice the 'this->' prefix to make GetLog() a dependent name
    }
    Log& Token(Log& l) const { return l << "Foo"; }
};
// logging inside a non-Context derived class
struct Bar
{
    void Func()
    {
        LOG << __func__ << std::endl;
    }
};
// logging inside a free function 
void Baz()
{
    LOG << __func__ << std::endl;
}
//-------------------------
int main()
{
    Foo<int> f;
    f.Func1();
    f.Func2();
    Bar b;
    b.Func();
    Baz();
    exit(0);
}

您可以使用声明显式地将Context::GetLog引入派生类的作用域:

template<typename T>
struct Foo : Context<Foo<T>>
{
    using Context<Foo<T>>::GetLog;
    // the rest as before
};

生活例子

嗯,有一个简单的解决方案,尽管它需要在上下文派生类中做一些额外的工作。

您可以通过基类名称而不是this->来使用限定,因此,例如使用Context<Foo<T>>::GetLog足以向编译器表明查找是依赖的(并且必须延迟到实例化)。

不幸的是,由于Context本身就是一个模板,它有点无聊;所以我们将使用另一个类(这里我称之为Base):

// 1. Wrap the generic GetLog into a `Base` class:
struct Base {
    static Log& GetLog() { return glog; }
};
// 2. Introduce a typedef into the derived class:
template <typename T>
struct Foo: Context<Foo<T>> {
    typedef Context<Foo<T>> Base;
    ...
};
// 3. Change the macro
#define LOG Base::GetLog()

这里,Base查找遵循典型的作用域规则,并找到最接近的Base…在最坏的情况下,默认为全局作用域中存在的那个。