Явная специализация шаблонов для const QString и результаты в нерешенных внешних - PullRequest
1 голос
/ 29 января 2012
#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<QString>() при вызове из-за первой высокоуровневой перегрузки. Хорошо, я подумал и изменил код:

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

Та же проблема. И я подумал, что сказал компилятору создать экземпляр openInternal<const QString&>, так почему он до сих пор жалуется на <class QString>? Я также попробовал это:

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

Тогда это работает. Практически нулевое снижение производительности также является вполне приемлемым решением, но я просто хочу понять, что здесь происходит.

Код выше был просто SSCCE, реальный код там , если кому-то интересно. Эта конкретная проблема связана с функциями gzopen()/gzdopen(), QuaGzipFilePrivate::open() и QuaGzipFile::open().

1 Ответ

2 голосов
/ 29 января 2012

Поскольку вы действительно меняете сигнатуру, я думаю, что вы на самом деле не хотите специализировать свой шаблон функции, а используете перегрузку. Вот полная тестовая программа с использованием 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);
}

Если вы #defined BROKEN в этом коде, он не будет работать.

Причина такого поведения заключается в том, что компилятор выбирает перегрузку на основе основного шаблона. Это никогда не добавит часть const&. После этого компилятор ищет потенциальные специализации выбранной перегрузки. Так как это никогда не вывело бы обозначение, используемое для специализации, это не взято.

Почему тогда f<std::string const&>(s) не получает специализацию? Для меня это так, пробуя как gcc, так и clang.

...