Понимание специализации шаблона члена С++

У меня есть следующий класс:

#pragma once
#include <string>
#include <iostream>

class testclass
{
public:
    template <class T> T item(const std::string& key)
    {
        std::cout << "non-specialized\n";
        return T();
    }
};

Для метода item я хотел бы предоставить специализацию для строк. Я пытаюсь сделать это следующим образом (в testclass.cpp):

#include "testclass.h"
#include <iostream>

template<> std::string testclass::item(const std::string& key)
{
    std::cout << "specialized\n";
    return std::reverse(key.begin(), key.end());
}

И затем я пытаюсь вызвать функцию следующим образом:

#include <iostream>
#include "testclass.h"

int main()
{
    testclass t;
    std::string key = "foo";
    t.item<int>(key);
    std::string s = t.item<std::string>(key);
    std::cout << s << std::endl;
}

Однако выход

$ ./a.out
non-specialized 
non-specialized
(empty line)

То, что я исключил, было

$ ./a.out
non-specialized 
specialized
oof

Как я могу сделать это правильно? Я использую g++ 4.5.2 для компиляции программы.

Edit

Решение - это перемещение всего определения специализации item на testclass.h(но не на класс). У меня были другие ошибки в программе, например, не включая <algorithm> (для обратного) и неправильное представление о том, что она вернет обратную строку. Для достижения исключенного поведения файл .cpp остается пустым, а содержимое заголовка выглядит следующим образом:

#pragma once
#include <string>
#include <iostream>
#include <algorithm>

class testclass
{
    public:
        template <class T> T item(const std::string& key)
        {
            std::cout << "non-specialized\n";
            return T();
        }
};

template<> std::string testclass::item(const std::string& key)
{
    std::cout << "specialized\n";
    std::string s = key;
    std::reverse(s.begin(), s.end());
    return s;
}

Ответы

Ответ 1

Проблема сводится к общей проблеме отсутствия шаблонов в файле заголовка. Компилятор при обработке main не видит специализации и генерирует свою собственную экземпляр шаблона generic для std::string. Это нарушение ODR, так как в этой же программе есть 2 разных специализации для std::string, но компилятор не требуется для ее диагностики.

Простым решением является объявить/определить специализацию в заголовке, чтобы компилятор мог либо использовать его, либо, по крайней мере, будет знать, что не генерировать специализацию из общей версии при обработке main

Ответ 2

К сожалению, это не вся история. Явная специализация функции "элемент" шаблона-члена должна быть уникальной во всей программе. Если бы вы создали другую систему перевода, которая реализовала другую функцию, которая выполняла ту же функцию, что и "основная", и связала ее с вашей программой, вы получили бы несколько определений. У вас есть несколько альтернатив: (1) объявить специализацию встроенной функцией в заголовке (не общее решение); (2) объявить специализацию в заголовке и определить его в файле .cpp(общий ответ). Это отличный пример важности понимания на фундаментальном уровне того, как компиляторы и компоновщики реализуют "физическое связывание" различных конструкций С++.