definição múltipla de especialização de template ao usar objetos diferentes

95

Quando eu uso um modelo especializado em arquivos de objeto diferentes, recebo um erro de "definição múltipla" ao vincular. A única solução que encontrei envolve o uso da função "embutida", mas parece apenas uma solução alternativa. Como faço para resolver isso sem usar a palavra-chave "inline"? Se isso não for possível, por quê?

Aqui está o código de exemplo:

paulo@aeris:~/teste/cpp/redef$ cat hello.h 
#ifndef TEMPLATE_H
#define TEMPLATE_H

#include <iostream>

template <class T>
class Hello
{
public:
    void print_hello(T var);
};

template <class T>
void Hello<T>::print_hello(T var)
{
    std::cout << "Hello generic function " << var << "\n";
}

template <> //inline
void Hello<int>::print_hello(int var)
{
    std::cout << "Hello specialized function " << var << "\n";
}

#endif

paulo@aeris:~/teste/cpp/redef$ cat other.h 
#include <iostream>

void other_func();

paulo@aeris:~/teste/cpp/redef$ cat other.c 
#include "other.h"

#include "hello.h"

void other_func()
{
    Hello<char> hc;
    Hello<int> hi;

    hc.print_hello('a');
    hi.print_hello(1);
}

paulo@aeris:~/teste/cpp/redef$ cat main.c 
#include "hello.h"

#include "other.h"

int main()
{
    Hello<char> hc;
    Hello<int> hi;

    hc.print_hello('a');
    hi.print_hello(1);

    other_func();

    return 0;
}

paulo@aeris:~/teste/cpp/redef$ cat Makefile
all:
    g++ -c other.c -o other.o -Wall -Wextra
    g++ main.c other.o -o main -Wall -Wextra

Finalmente:

paulo@aeris:~/teste/cpp/redef$ make
g++ -c other.c -o other.o -Wall -Wextra
g++ main.c other.o -o main -Wall -Wextra
other.o: In function `Hello<int>::print_hello(int)':
other.c:(.text+0x0): multiple definition of `Hello<int>::print_hello(int)'
/tmp/cc0dZS9l.o:main.c:(.text+0x0): first defined here
collect2: ld returned 1 exit status
make: ** [all] Erro 1

Se eu descomentar o "inline" dentro de hello.h, o código será compilado e executado, mas isso parece apenas uma espécie de "solução alternativa" para mim: e se a função especializada for grande e usada muitas vezes? Vou pegar um grande binário? Existe alguma outra maneira de fazer isso? Se sim, como? Se não, por quê?

Tentei procurar respostas, mas tudo que consegui foi "usar inline" sem qualquer explicação adicional.

obrigado

pzanoni
fonte
6
colocar a implementação especializada real em .cpp em vez de arquivo de cabeçalho
Anycorn

Respostas:

129

Intuitivamente, quando você especializa totalmente algo, ele não depende mais de um parâmetro de modelo - então, a menos que você faça a especialização embutida, você precisa colocá-la em um arquivo .cpp em vez de .h ou você acabará violando o uma regra de definição, como diz David. Observe que quando você especializa parcialmente os modelos, as especializações parciais ainda dependem de um ou mais parâmetros do modelo, portanto, eles ainda vão para um arquivo .h.

Stuart Golodetz
fonte
Hmmm, ainda estou um pouco confuso sobre como isso quebra o ODR. Porque você só define o modelo totalmente especializado uma vez. Você pode estar criando o objeto várias vezes em arquivos de objetos diferentes (ou seja, neste caso ele é instanciado em other.ce main.c), mas o próprio objeto original é definido apenas em um arquivo - neste caso hello.h.
Justin Liang
3
@JustinLiang: O cabeçalho está incluído em dois arquivos .c separados - que têm o mesmo efeito como se você tivesse escrito seu conteúdo (incluindo a especialização completa) diretamente nos arquivos nos quais está incluído nos locais relevantes. A Regra de Uma Definição (ver en.wikipedia.org/wiki/One_Definition_Rule ) diz (entre outras coisas): "Em todo o programa, um objeto ou função não embutida não pode ter mais de uma definição". Nesse caso, a especialização completa do modelo de função é basicamente igual a uma função normal, portanto, a menos que seja embutida, ela não pode ter mais de uma definição.
Stuart Golodetz
Hmmm, percebi que quando não temos um modelo de especialização, esse erro não aparece. Digamos que tenhamos duas funções diferentes que foram definidas no arquivo de cabeçalho, fora da classe, elas ainda funcionarão sem o inline? Por exemplo: pastebin.com/raw.php?i=bRaiNC7M . Eu fiz essa aula e incluí em dois arquivos. Isso não teria "o mesmo efeito que se você tivesse escrito o conteúdo" diretamente nos dois arquivos e, portanto, haverá um erro de definição múltipla?
Justin Liang,
@Justin Liang, seu código de cabeçalho baseado em classe ainda violará o ODR se incluído em vários arquivos, a menos que as definições de função estejam dentro do corpo da classe.
haripkannan de
Portanto, se minha definição de membro estático for precedida por, template <typename T>então, ela poderá entrar em um cabeçalho e, se for, template<>não será?
Violet Giraffe
49

A palavra inline- chave tem mais a ver com dizer ao compilador que o símbolo estará presente em mais de um arquivo de objeto sem violar a Regra de Uma Definição do que com o embutimento real, que o compilador pode decidir fazer ou não fazer.

O problema que você está vendo é que sem o inline, a função será compilada em todas as unidades de tradução que incluam o cabeçalho, violando o ODR. Adicionar inlineaí é o caminho certo a seguir. Caso contrário, você pode encaminhar a declaração de especialização e fornecê-la em uma única unidade de tradução, como faria com qualquer outra função.

David Rodríguez - dribeas
fonte
22

Você instanciou explicitamente um modelo em seu cabeçalho (void Hello<T>::print_hello(T var) ). Isso criará várias definições. Você pode resolver isso de duas maneiras:

1) Faça sua instanciação embutida.

2) Declare a instanciação em um cabeçalho e a implemente em um cpp.

Edward Strange
fonte
Na verdade, há uma terceira maneira que é colocá-los em um namespace sem nome ... que é semelhante a ter estático em C.
Alexis Wilke
4
Isso não é válido aqui. Uma especialização de modelo precisa estar no mesmo namespace que o modelo original.
Edward Strange
0

Aqui está uma parte do padrão C ++ 11 relacionada a esse problema:

Uma especialização explícita de um modelo de função é embutida apenas se for declarada com o especificador embutido ou definida como excluída, e independentemente de seu modelo de função ser embutido. [Exemplo:

template void f (T) {/ * ... /} template inline T g (T) {/ ... * /}

template <> inline void f <> (int) {/ * ... /} // OK: inline template <> int g <> (int) {/ ... * /} // OK: não inline - fim exemplo]

Portanto, se você fizer algumas especializações explícitas (também conhecidas como completas) de modelos no *.harquivo, ainda precisará inlineajudá-lo a se livrar da violação do ODR .

Francis
fonte