const QString& 的显式模板专业化导致未解决的外部

发布于 2024-12-29 15:29:48 字数 2899 浏览 2 评论 0 原文

#include <QFile>
#include <QString>

// this is some sort of low-level C function
void lowLevelOpenFD(int fd)
{
    qDebug("Opened by fd: %d", fd);
}

// as well as this one
void lowLevelOpenName(const char *name)
{
    qDebug("Opened by name: %s", name);
}

// this is a wrapper around low-level functions
template<typename FileId>
void callLowLevelOpen(FileId id);

template<>
void callLowLevelOpen(const QString &fileName)
{
    lowLevelOpenName(QFile::encodeName(fileName).constData());
}

template<>
void callLowLevelOpen(int fd)
{
    lowLevelOpenFD(fd);
}

// this is the function where the most stuff happens
template<typename FileId>
void openInternal(FileId id)
{
    // lots of useful stuff goes here
    // now we call one of the two low level functions
    callLowLevelOpen(id);
    // more useful stuff
}

// this is high-level interface to the "open by name" function
void openFile()
{
    QString name = "file";
    openInternal(name);
}

// this is high-level interface to the "open by FD" function
void openFile(int fd)
{
    openInternal(fd);
}

int main()
{
    openFile();
    openFile(17);
    return 0;
}

问题在于,据我所知,上面的示例会发生

error LNK2019: unresolved external symbol "void __cdecl callLowLevelOpen<class QString>(class QString)" (??$callLowLevelOpen@VQString@@@@YAXVQString@@@Z) referenced in function "void __cdecl openInternal<class QString>(class QString)" (??$openInternal@VQString@@@@YAXVQString@@@Z)

这种情况,因为编译器在从第一个高级重载调用时会实例化 openInternal() 。好吧,于是我思考并修改了代码:

// this is high-level interface to the "open by name" function
void openFile()
{
    QString name = "file";
    openInternal<const QString&>(name);
}

同样的问题。我以为我告诉编译器实例化 openInternal,那么为什么它仍然抱怨 呢?我也尝试过这个:

// this is high-level interface to the "open by name" function
void openFile()
{
    QString name = "file";
    openInternal<const QString&>(static_cast<const QString&>(name));
}

现在这看起来很愚蠢,但仍然不起作用。我无法显式地专门化 openInternal() 一个,因为它太大了,而这种模板化混乱的目的就是为了避免不必要的代码重复。我不能仅仅重命名低级函数来将它们变成重载函数,因为它们位于第三方 C 库中。我唯一能做的就是将第一个 callLowLevelOpen() 专业化替换为

template<>
void callLowLevelOpen(QString fileName)
{
    lowLevelOpenName(QFile::encodeName(fileName).constData());
}

Then it Works。性能损失几乎为零,因此这是一个完全有效的解决方法,但我只想了解这里发生了什么。

上面的代码只是一个SSCCE,真正的代码是 如果有人感兴趣的话。此特定问题与 gzopen()/gzdopen()QuaGzipFilePrivate::open()QuaGzipFile::open() 函数有关。

#include <QFile>
#include <QString>

// this is some sort of low-level C function
void lowLevelOpenFD(int fd)
{
    qDebug("Opened by fd: %d", fd);
}

// as well as this one
void lowLevelOpenName(const char *name)
{
    qDebug("Opened by name: %s", name);
}

// this is a wrapper around low-level functions
template<typename FileId>
void callLowLevelOpen(FileId id);

template<>
void callLowLevelOpen(const QString &fileName)
{
    lowLevelOpenName(QFile::encodeName(fileName).constData());
}

template<>
void callLowLevelOpen(int fd)
{
    lowLevelOpenFD(fd);
}

// this is the function where the most stuff happens
template<typename FileId>
void openInternal(FileId id)
{
    // lots of useful stuff goes here
    // now we call one of the two low level functions
    callLowLevelOpen(id);
    // more useful stuff
}

// this is high-level interface to the "open by name" function
void openFile()
{
    QString name = "file";
    openInternal(name);
}

// this is high-level interface to the "open by FD" function
void openFile(int fd)
{
    openInternal(fd);
}

int main()
{
    openFile();
    openFile(17);
    return 0;
}

The problem is that the example above results in

error LNK2019: unresolved external symbol "void __cdecl callLowLevelOpen<class QString>(class QString)" (??$callLowLevelOpen@VQString@@@@YAXVQString@@@Z) referenced in function "void __cdecl openInternal<class QString>(class QString)" (??$openInternal@VQString@@@@YAXVQString@@@Z)

As far as I can see it happens because the compiler instantiates openInternal<QString>() when it's called from the first high-level overload. OK, so I thought and modified the code:

// this is high-level interface to the "open by name" function
void openFile()
{
    QString name = "file";
    openInternal<const QString&>(name);
}

The same problem. And I thought I told the compiler to instantiate the openInternal<const QString&>, so why it is still complains about <class QString>? I also tried this one:

// this is high-level interface to the "open by name" function
void openFile()
{
    QString name = "file";
    openInternal<const QString&>(static_cast<const QString&>(name));
}

Now this just looks silly and it still doesn't work. I can't explicitly specialize the openInternal() one because it's too large and the very point of this templated mess is to avoid unnecessary code duplication. I can't just rename the low level functions to turn them into overloaded ones, because they are in a third-party C library. The only thing I can do is to replace the first callLowLevelOpen() specialization with

template<>
void callLowLevelOpen(QString fileName)
{
    lowLevelOpenName(QFile::encodeName(fileName).constData());
}

Then it works. There is also virtually zero performance penalty so this is a perfectly valid workaround, but I just want to understand what is going on here.

The code above was just an SSCCE, the real code is there if anyone interested. This particular issue is with the gzopen()/gzdopen(), QuaGzipFilePrivate::open() and QuaGzipFile::open() functions.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

南薇 2025-01-05 15:29:48

由于您实际上更改了签名,我认为您实际上不想专门化您的函数模板,而是使用重载。这是使用 std::string 的完整测试程序(如果问题也可以在那里重现,我不知何故更喜欢仅依赖于标准类):

#include <string>

template <typename T> void f(T);
// #define BROKEN
#if defined(BROKEN)
template <> void f(std::string const&) {}
#else
void f(std::string const&) {}
#endif

int main()
{
    std::string s;
    f(s);
}

如果您在这段代码将不起作用。

出现此行为的原因是编译器根据主模板选择重载。这永远不会添加 const& 部分。完成此操作后,编译器会查找所选重载的潜在特化。由于这永远不会推导出用于专业化的符号,因此不会被拾取。

那么为什么 f(s) 没有获得专业化呢?对我来说,尝试使用 gcc 和 clang 。

Since you actually change the signature, I think you actually don't want to specialize your function template but rather use overloading. Here is complete test program using std::string (I somehow prefer depending only on standard classes if the problem can be reproduced there as well):

#include <string>

template <typename T> void f(T);
// #define BROKEN
#if defined(BROKEN)
template <> void f(std::string const&) {}
#else
void f(std::string const&) {}
#endif

int main()
{
    std::string s;
    f(s);
}

If you #defined BROKEN in this code it won't work.

The reason for this behavior is that the compiler choose the overload based on the primary template. This will never add the const& part. Once this is done the compiler looks for potential specializations of the chosen overload. Since this will never have deduced the notation used for specialization this isn't picked up.

Why then is the f<std::string const&>(s) not picking up the specialization? For me it is, trying with both gcc and clang.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文