Em Java, você pode definir classe genérica que aceita apenas tipos que estendem a classe de sua escolha, por exemplo:
public class ObservableList<T extends List> {
...
}
Isso é feito usando a palavra-chave "extends".
Existe algum equivalente simples a essa palavra-chave em C ++?
Respostas:
Sugiro usar o recurso de declaração estática do Boost em conjunto com
is_base_of
a biblioteca Boost Type Traits:Em alguns outros casos mais simples, você pode simplesmente declarar um modelo global adiante, mas apenas defini-lo (explicitamente ou parcialmente) para os tipos válidos:
[EDIT Menor 12/12/2013: o uso de um modelo declarado, mas não definido, resultará em mensagens de erro do vinculador , não do compilador.]
fonte
myBaseType
exatamente. Antes de descartar o Boost, você deve saber que a maioria é de código de modelo apenas de cabeçalho - portanto, não há memória ou tempo em tempo de execução para itens que você não usa. Além disso, as coisas específicas que você usaria aqui (BOOST_STATIC_ASSERT()
eis_base_of<>
) podem ser implementadas usando apenas declarações (ou seja, nenhuma definição real de funções ou variáveis), para que não ocupem espaço ou tempo.static_assert(std::is_base_of<List, T>::value, "T must extend list")
.my_template<int> x;
ouemy_template<float**> y;
verificar se o compilador permite e, em seguida, declarar uma variávelmy_template<char> z;
e verificar se não.Isso normalmente não é garantido em C ++, como outras respostas aqui observadas. Em C ++, tendemos a definir tipos genéricos com base em outras restrições além de "herda desta classe". Se você realmente queria fazer isso, é muito fácil fazer no C ++ 11 e
<type_traits>
:Isso quebra muitos dos conceitos que as pessoas esperam em C ++. É melhor usar truques como definir suas próprias características. Por exemplo, talvez
observable_list
queira aceitar qualquer tipo de recipiente que tem os typedefsconst_iterator
e umbegin
eend
função membro que retornaconst_iterator
. Se você restringir isso às classes que herdamlist
, um usuário que tenha seu próprio tipo que não herda,list
mas fornece essas funções de membro e typedefs, não poderá usar o seuobservable_list
.Existem duas soluções para esse problema, uma delas é não restringir nada e confiar na digitação do pato. Uma grande desvantagem dessa solução é que ela envolve uma quantidade enorme de erros que podem ser difíceis para os usuários entenderem. Outra solução é definir características para restringir o tipo fornecido para atender aos requisitos da interface. A grande desvantagem dessa solução é que envolve reduções extras que podem ser vistas como irritantes. No entanto, o lado positivo é que você poderá escrever suas próprias mensagens de erro à la
static_assert
.Para completar, a solução para o exemplo acima é fornecida:
Existem muitos conceitos mostrados no exemplo acima que mostram os recursos do C ++ 11. Alguns termos de pesquisa para os curiosos são modelos variados, SFINAE, expressão SFINAE e características de tipo.
fonte
template<class T:list>
esse conceito é ofensivo. Obrigado pela dica.A solução simples, que ninguém mencionou ainda, é simplesmente ignorar o problema. Se eu tentar usar um
int
tipo de modelo em um modelo de função que espera uma classe de contêiner como vetor ou lista, receberei um erro de compilação. Bruto e simples, mas resolve o problema. O compilador tentará usar o tipo que você especificar e, se isso falhar, gera um erro de compilação.O único problema é que as mensagens de erro que você recebe serão difíceis de ler. No entanto, é uma maneira muito comum de fazer isso. A biblioteca padrão está cheia de modelos de função ou classe que esperam determinado comportamento do tipo de modelo e não fazem nada para verificar se os tipos usados são válidos.
Se você deseja mensagens de erro mais agradáveis (ou se deseja capturar casos que não produziriam um erro de compilador, mas ainda não fazem sentido), você pode, dependendo da complexidade que deseja criar, use a declaração estática do Boost ou a biblioteca Boost concept_check.
Com um compilador atualizado, você tem um built_in
static_assert
, que pode ser usado.fonte
T
e de onde esse código é chamado? Sem algum contexto, não tenho chance de entender esse trecho de código. Mas o que eu disse é verdade. Se você tentar chamartoString()
um tipo que não possui umatoString
função de membro, receberá um erro de compilação.Podemos usar
std::is_base_of
estd::enable_if
:(
static_assert
podem ser removidos, as classes acima podem ser implementadas sob medida ou usadas a partir do boost se não pudermos fazer referênciatype_traits
)fonte
Tanto quanto sei, atualmente não é possível em C ++. No entanto, há planos de adicionar um recurso chamado "conceitos" no novo padrão C ++ 0x que forneça a funcionalidade que você está procurando. Este artigo da Wikipedia sobre conceitos de C ++ explicará mais detalhadamente.
Sei que isso não resolve o seu problema imediato, mas existem alguns compiladores C ++ que já começaram a adicionar recursos do novo padrão; portanto, talvez seja possível encontrar um compilador que já tenha implementado o recurso de conceitos.
fonte
static_assert
e SFINAE, como mostram as outras respostas. O problema restante para alguém vindo de Java ou C # ou Haskell (...) é que o compilador C ++ 20 não faz verificação de definição com relação aos conceitos necessários, como Java e C # fazem.Acho que todas as respostas anteriores perderam de vista a floresta para as árvores.
Os genéricos Java não são iguais aos modelos ; eles usam apagamento de tipo , que é uma técnica dinâmica , em vez de compilar o polimorfismo em tempo , que é uma técnica estática . Deveria ser óbvio por que essas duas táticas muito diferentes não se adaptam bem.
Em vez de tentar usar uma construção de tempo de compilação para simular uma de execução, vejamos o que
extends
realmente faz: de acordo com o Stack Overflow e a Wikipedia , extends é usado para indicar subclassificação.C ++ também suporta subclassificação.
Você também mostra uma classe de contêiner, que está usando apagamento de tipo na forma de um genérico, e se estende para executar uma verificação de tipo. No C ++, você deve executar o tipo de mecanismo de apagamento, o que é simples: faça um ponteiro para a superclasse.
Vamos envolvê-lo em um typedef, para facilitar o uso, em vez de criar uma classe inteira, et voila:
typedef std::list<superclass*> subclasses_of_superclass_only_list;
Por exemplo:
Agora, parece que List é uma interface, representando um tipo de coleção. Uma interface em C ++ seria apenas uma classe abstrata, ou seja, uma classe que implementa nada além de métodos virtuais puros. Usando esse método, você pode implementar facilmente seu exemplo de java em C ++, sem qualquer conceito ou especialização de modelo. Ele também teria um desempenho tão lento quanto os genéricos do estilo Java devido às pesquisas da tabela virtual, mas isso geralmente pode ser uma perda aceitável.
fonte
Um equivalente que aceita apenas tipos T derivados do tipo Lista se parece com
fonte
Resumo executivo: não faça isso.
A resposta de j_random_hacker diz como fazer isso. No entanto, também gostaria de salientar que você não deve fazer isso. O ponto principal dos modelos é que eles podem aceitar qualquer tipo compatível, e as restrições de tipo de estilo Java quebram isso.
As restrições de tipo do Java são um bug, não um recurso. Eles estão lá porque o Java digita apagamento em genéricos, portanto, o Java não consegue descobrir como chamar métodos com base apenas no valor dos parâmetros de tipo.
C ++, por outro lado, não tem essa restrição. Os tipos de parâmetro do modelo podem ser de qualquer tipo compatível com as operações com as quais são usados. Não precisa haver uma classe base comum. Isso é semelhante ao "Duck Typing" do Python, mas feito em tempo de compilação.
Um exemplo simples mostrando o poder dos modelos:
Essa função de soma pode somar um vetor de qualquer tipo que suporte as operações corretas. Ele funciona com ambas as primitivas, como int / long / float / double, e tipos numéricos definidos pelo usuário que sobrecarregam o operador + =. Heck, você pode até usar esta função para juntar strings, já que elas suportam + =.
Nenhum boxe / unboxing de primitivos é necessário.
Observe que ele também constrói novas instâncias de T usando T (). Isso é trivial no C ++ usando interfaces implícitas, mas não é realmente possível em Java com restrições de tipo.
Embora os modelos C ++ não tenham restrições de tipo explícitas, eles ainda são do tipo seguro e não serão compilados com código que não suporta as operações corretas.
fonte
Isso não é possível em C ++ simples, mas você pode verificar os parâmetros do modelo em tempo de compilação através da Verificação de Conceito, por exemplo, usando o BCCL do Boost .
A partir do C ++ 20, os conceitos estão se tornando um recurso oficial da linguagem.
fonte
Verifique se as classes derivadas herdam a estrutura FooSecurity e se o compilador ficará chateado nos lugares certos.
fonte
Type::FooSecurity
é usado na classe de modelo. Se a classe, passada no argumento do modelo, não tiverFooSecurity
, a tentativa de usá-lo causa um erro. É certo que, se a classe passada no argumento do modelo não possui FooSecurity, ela não é derivadaBase
.Uso do conceito C ++ 20
https://en.cppreference.com/w/cpp/language/constraints cppreference está fornecendo o caso de uso de herança como um exemplo explícito de conceito:
Para várias bases, acho que a sintaxe será:
Parece que o GCC 10 o implementou: https://gcc.gnu.org/gcc-10/changes.html e você pode obtê-lo como um PPA no Ubuntu 20.04 . https://godbolt.org/ Meu GCC 10.1 local ainda não reconheceu
concept
, portanto, não tenho certeza do que está acontecendo.fonte
Não.
Dependendo do que você está tentando realizar, pode haver substitutos adequados (ou até melhores).
Eu olhei através de algum código STL (no Linux, acho que é o derivado da implementação da SGI). Possui "afirmações conceituais"; por exemplo, se você precisar de um tipo que entenda
*x
e++x
, a asserção de conceito conteria esse código em uma função do-nothing (ou algo semelhante). Requer alguma sobrecarga, portanto, pode ser inteligente colocá-lo em uma macro cuja definição depende#ifdef debug
.Se o relacionamento da subclasse é realmente o que você quer saber, você pode afirmar no construtor que
T instanceof list
(exceto que "está escrito" de maneira diferente em C ++). Dessa forma, você pode testar a saída do compilador, sem poder verificar isso por você.fonte
Não existe uma palavra-chave para esse tipo de verificação, mas você pode inserir um código que falhe pelo menos de maneira ordenada:
(1) Se você deseja que um modelo de função aceite apenas parâmetros de uma determinada classe base X, atribua-o a uma referência X em sua função. (2) Se você deseja aceitar funções, mas não primitivas ou vice-versa, ou deseja filtrar classes de outras maneiras, chame uma função auxiliar de modelo (vazia) dentro da sua função, definida apenas para as classes que deseja aceitar.
Você pode usar (1) e (2) também nas funções de membro de uma classe para forçar essas verificações de tipo em toda a classe.
Você provavelmente pode colocá-lo em alguma macro inteligente para aliviar sua dor. :)
fonte
Bem, você pode criar seu modelo lendo algo assim:
No entanto, isso torna a restrição implícita, e você não pode fornecer apenas algo que se pareça com uma lista. Existem outras maneiras de restringir os tipos de contêineres usados, por exemplo, usando tipos de iteradores específicos que não existem em todos os contêineres, mas novamente isso é mais implícito do que explícito.
Que eu saiba, não existe no padrão atual uma construção que espelhe a instrução Java em toda a extensão.
Existem maneiras de restringir os tipos que você pode usar dentro de um modelo que você escreve usando typedefs específicos dentro do seu modelo. Isso garantirá que a compilação da especialização de modelo para um tipo que não inclua esse typedef específico falhará, para que você possa apoiar / não selecionar seletivamente certos tipos.
No C ++ 11, a introdução de conceitos deve facilitar isso, mas não acho que ele faça exatamente o que você deseja.
fonte