[C++] Por que ponteiros constantes não servem de parâmetro de template em std::list?

12 respostas
D

A tentativa de incluir esse código me gera um erro de compilação, por utilizar um ponteiro const como parametro de template, por que?

std::list<Type * const> types;

12 Respostas

E

Boa pergunta. Compilei o programa abaixo com MS C++ e ele não reclamou de nada.

#include <list>
#include <iostream>
#include <string>

using namespace std;

class Tipo {
public:
    Tipo () { }
	virtual ~Tipo () { }
private:
    string s;
    double f;
};

int main (int argc, char *argv[]) {
	list<Tipo> tipos;
	list<Tipo*> tipos2;
	list<Tipo const*> tipos3;
	list<Tipo * const> tipos4;
}
E

De fato, no g++ 4.5.3 ele me deu o seguinte erro:

$ g++ -O ConstType ConstType.cpp
In file included from /usr/lib/gcc/i686-pc-cygwin/4.5.3/include/c++/i686-pc-cygw
in/bits/c++allocator.h:34:0,
                 from /usr/lib/gcc/i686-pc-cygwin/4.5.3/include/c++/bits/allocat
or.h:48,
                 from /usr/lib/gcc/i686-pc-cygwin/4.5.3/include/c++/list:62,
                 from ConstType.cpp:1:
/usr/lib/gcc/i686-pc-cygwin/4.5.3/include/c++/ext/new_allocator.h: In instantiat
ion of '__gnu_cxx::new_allocator<Tipo* const>':
/usr/lib/gcc/i686-pc-cygwin/4.5.3/include/c++/bits/allocator.h:87:5:   instantia
ted from 'std::allocator<Tipo* const>'
/usr/lib/gcc/i686-pc-cygwin/4.5.3/include/c++/bits/stl_list.h:294:9:   instantia
ted from 'std::_List_base<Tipo* const, std::allocator<Tipo* const> >'
/usr/lib/gcc/i686-pc-cygwin/4.5.3/include/c++/bits/stl_list.h:418:5:   instantia
ted from 'std::list<Tipo* const>'
ConstType.cpp:15:21:   instantiated from here
/usr/lib/gcc/i686-pc-cygwin/4.5.3/include/c++/ext/new_allocator.h:79:7: error: '
const _Tp* __gnu_cxx::new_allocator<_Tp>::address(const _Tp&) const [with _Tp =
Tipo* const, const _Tp* = Tipo* const*, const _Tp& = Tipo* const&]' cannot be ov
erloaded
/usr/lib/gcc/i686-pc-cygwin/4.5.3/include/c++/ext/new_allocator.h:76:7: error: w
ith '_Tp* __gnu_cxx::new_allocator<_Tp>::address(_Tp&) const [with _Tp = Tipo* c
onst, _Tp* = Tipo* const*, _Tp& = Tipo* const&]'

Pelo que imagino, o g++ é mais rigoroso nesse caso, porque ele insiste em determinar como é que se pode construir um elemento Tipo* const. Nesse caso, em que não pode haver um construtor - ele sempre deve ser definido a partir de uma atribuição explícita - ele simplesmente desistiu e gerou a mensagem de erro bastante críptica, mas exata.

D

compilei o mesmo código que você acaba de mostrar e o erro de compilação retorna:
make all
Building file: …/main.cpp
Invoking: Cross G++ Compiler
g++ -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"main.d" -MT"main.d" -o “main.o” "…/main.cpp"
In file included from /usr/include/c++/4.4/x86_64-linux-gnu/bits/c++allocator.h:34,
from /usr/include/c++/4.4/bits/allocator.h:48,
from /usr/include/c++/4.4/list:62,
from …/main.cpp:8:
/usr/include/c++/4.4/ext/new_allocator.h: In instantiation of ?__gnu_cxx::new_allocator<Tipo* const>?:
/usr/include/c++/4.4/bits/allocator.h:87: instantiated from ?std::allocator<Tipo* const>?
/usr/include/c++/4.4/bits/stl_list.h:294: instantiated from ?std::_List_base<Tipo* const, std::allocator<Tipo* const> >?
/usr/include/c++/4.4/bits/stl_list.h:418: instantiated from ?std::list<Tipo* const, std::allocator<Tipo* const> >?
…/main.cpp:27: instantiated from here
/usr/include/c++/4.4/ext/new_allocator.h:79: error: ?const _Tp* __gnu_cxx::new_allocator<_Tp>::address(const _Tp&) const [with _Tp = Tipo* const]? cannot be overloaded
/usr/include/c++/4.4/ext/new_allocator.h:76: error: with ?_Tp* __gnu_cxx::new_allocator<_Tp>::address(_Tp&) const [with _Tp = Tipo* const]?
make: *** [main.o] Error 1

esta é a versão do meu compilador:
Using built-in specs.
Target: x86_64-linux-gnu
Configured with: …/src/configure -v --with-pkgversion=‘Ubuntu 4.4.3-4ubuntu5.1’ --with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --enable-shared --enable-multiarch --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.4 --program-suffix=-4.4 --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-plugin --enable-objc-gc --disable-werror --with-arch-32=i486 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.4.3 (Ubuntu 4.4.3-4ubuntu5.1)

D

Ainda não entendo o porque

E

De certo modo é bastante fácil entender o que ocorre.

Tipo * const é diferente de Tipo const *

O primeiro indica que a variável (ou parâmetro) que tem esse tipo não pode ser alterada:

Tipo t; Tipo u;
Tipo * const pT = &t;
pT->x = 10; // OK porque é pT que não pode ser mudada, não a posiçao de memória t
pT = &u; // isto gera um erro de compilação porque não posso mudar a variável

O segundo indica que o valor para o qual apontamos é que não pode ser alterado:

Tipo t; Tipo u;
Tipo const *pT = &t;
pT->x = 10; // isto gera o valor para o qual apontamos, portanto gera um erro de compilação
pT = &u; // não tem problema fazer isto, porque é a variável que pode ser alterada
D

Isso eu entendo entanglement o que não entendo é o erro de compilação, eu realmente desejo uma lista de ponteiros constantes (ponteiros que não apontam novos endereçamentos além do definido na declaração).

No meu caso específico queria uma lista de ponteiros objetos de um determinado tipo que fosse utilizada apenas para consultas e cópias, então a definição seira

D

o mesmo erro ocorre utilizando ao invés do ponteiro constante uma referência o que tem a mesma funcionalidade:

E

É a mesma ideia. Uma referência não pode ser inicializada DEPOIS de sua declaração.

Note que em Java há um conceito semelhante (final), mas em Java, devido ao conceito de análise de fluxo que o compilador é obrigado a efetuar parcialmente, é possível você ter um troço chamado “blank final”. Procureo por “blank final” na Java Language Specification.

G

Esse tópico me deixou curioso, fiquei acompanhando para ver se aparecia uma explicação simples, e dei umas xeretadas por aí enquanto isso.

Pelos fóruns tem algumas pessoas com essa dúvida e a resposta que costumam dar é que segundo a especificação os containers da STL só aceitam objetos que sejam “assignable and copy constructable” o que não acontece com uma variável Tipo * const

Só isso já é suficiente para matar a questão: não pode fazer o que você quer porque a especificação não permite e pronto!

Mas assim como você fiquei tentando entender o porque… do nosso ponto de vista de “usuário” da biblioteca não tem porque não permitir o *const, seria até desejável para alguns (que é o seu caso).

Só que pensando na implementação do template, acontecem alguns problemas. Em uma estrutura de dados é comum precisar manipular os elementos, atribuir novos valores, alocar dinamicamente um array do tipo especificado, etc. Tudo isso fica limitado com ponteiros constantes (ou referências, o que tem o mesmo efeito).

Imagine que em um determinado trecho do template exista o seguinte:

T var = ...;
...
var = newValue;

Na hora de compilar o template essa variavel vai ser definida como const, e consequentemente dá pau na outra linha.

Então não acredito que essa proibição seja um recurso proposital, mas é uma limitação técnica ao se criar estruturas de dados com templates.

D
gomesrod:
Esse tópico me deixou curioso, fiquei acompanhando para ver se aparecia uma explicação simples, e dei umas xeretadas por aí enquanto isso.

Pelos fóruns tem algumas pessoas com essa dúvida e a resposta que costumam dar é que segundo a especificação os containers da STL só aceitam objetos que sejam "assignable and copy constructable" o que não acontece com uma variável Tipo * const

Só isso já é suficiente para matar a questão: não pode fazer o que você quer porque a especificação não permite e pronto!

Mas assim como você fiquei tentando entender o porque... do nosso ponto de vista de "usuário" da biblioteca não tem porque não permitir o *const, seria até desejável para alguns (que é o seu caso).

Só que pensando na implementação do template, acontecem alguns problemas. Em uma estrutura de dados é comum precisar manipular os elementos, atribuir novos valores, alocar dinamicamente um array do tipo especificado, etc. Tudo isso fica limitado com ponteiros constantes (ou referências, o que tem o mesmo efeito).

Imagine que em um determinado trecho do template exista o seguinte:
T var = ...;
...
var = newValue;

Na hora de compilar o template essa variavel vai ser definida como const, e consequentemente dá pau na outra linha.

Então não acredito que essa proibição seja um recurso proposital, mas é uma limitação técnica ao se criar estruturas de dados com templates.

Realmente devia der lido a especificação, muito obrigado, mas quanto ao problema da definição de templates com tipos "no assignable and no copy constructable" podia ser contornado com um encapsulamento dos elementos, um exemplo rude seria algo como:
template <typename T>
class Capsule {
	void * const element;
public:
	Capsule(void *element) : element(element) {}
	//...
	T operator*() {
		return static_cast<T>(element);
	}
};

template <typename T>
class ListCapsules {
	typedef Capsule<T> newType;
	list<newType> capsulesList;
public:
	ListCapsules() {}

	//...

	void push_back(T &obj) {
		newType capsule = obj;
		capsulesList.push_back(capsule);
	}

	//...

	T &front() {
		newType capsule = capsulesList.front();
		return *capsule;
	}

	//...
};

sua utilização seguiria naturalmente o proposito para o cliente:

Tipo * const objTipo = new Tipo();
	ListCapsules<Tipo * const> listCapsules;
	listCapsules.push_back(objTipo);
	typeof(objTipo) objTipoRecovered = listCapsules.front();

	delete objTipo;

Vou dar uma olhada nos fontes da stl mas não entendo o porque de não disponibilizar tal recurso

D

Ainda sim entendo que esse novo tipo de lista encapsulada só serviria para tratar de ponteiros constantes a vários tipos e apenas.
Deveria ser criada uma lista para o tratamento específico deste.

D

Já quanto ao uso de referências não há desculpas com o uso de encapsulamento dos elementos com neste exemplo:

template &lt;typename T&gt;
class Capsule {
	T element;
public:
	Capsule(T &element) :
		element(element)
	{}
	//...
	T operator*() {
		return element;
	}
};

template &lt;typename T&gt;
class ListCapsules {
	typedef Capsule&lt;T&gt; newType;
	list&lt;newType&gt; capsulesList;
public:
	ListCapsules() {}

	//...

	void push_back(T &obj) {
		newType capsule = obj;
		capsulesList.push_back(capsule);
	}

	//...

	const T &front() {
		newType capsule = capsulesList.front();
		return *capsule;
	}

	//...
};

o cliente não precisaria se preocupar com nada:

ListCapsules&lt;int &&gt; listCapsules2;
	listCapsules2.push_back(nR);

	typeof(nR) recovered2 = listCapsules2.front();
	std::cout &lt;&lt; recovered2 &lt;&lt; std::endl;

funcionaria da mesma forma que

ListCapsules&lt;int&gt; listCapsules2;
	listCapsules2.push_back(nR);

	typeof(nR) recovered2 = listCapsules2.front();
	std::cout &lt;&lt; recovered2 &lt;&lt; std::endl;
Criado 14 de janeiro de 2013
Ultima resposta 15 de jan. de 2013
Respostas 12
Participantes 3