Estou pedindo um truque de modelo para detectar se uma classe tem uma função de membro específica de uma determinada assinatura.
O problema é semelhante ao citado aqui http://www.gotw.ca/gotw/071.htm, mas não é o mesmo: no item do livro de Sutter, ele respondeu à pergunta que uma classe C DEVE FORNECER a uma função membro com uma assinatura específica, caso contrário, o programa não será compilado. No meu problema, preciso fazer algo se uma classe tiver essa função, caso contrário, faça "algo mais".
Um problema semelhante foi enfrentado pelo boost :: serialization, mas não gosto da solução que eles adotaram: uma função de modelo que chama por padrão uma função livre (que você precisa definir) com uma assinatura específica, a menos que você defina uma função-membro específica ( no caso "serialize", que usa 2 parâmetros de um determinado tipo) com uma assinatura específica, caso contrário, ocorrerá um erro de compilação. Isso é para implementar serialização intrusiva e não intrusiva.
Não gosto dessa solução por dois motivos:
- Para não ser intrusivo, você deve substituir a função global "serialize" que está no namespace boost :: serialization, para que você tenha EM SEU CÓDIGO DO CLIENTE para abrir o aumento do namespace e a serialização do namespace!
- A pilha para resolver essa bagunça era de 10 a 12 invocações de funções.
Preciso definir um comportamento personalizado para classes que não possuem essa função de membro e minhas entidades estão dentro de espaços para nome diferentes (e não quero substituir uma função global definida em um espaço para nome enquanto estou em outro)
Você pode me dar uma dica para resolver esse quebra-cabeça?
Respostas:
Não tenho certeza se o entendi corretamente, mas você pode explorar o SFINAE para detectar a presença de funções em tempo de compilação. Exemplo do meu código (testa se a classe possui a função de membro size_t used_memory () const).
fonte
size_t(std::vector::*p)() = &std::vector::size;
,.Aqui está uma possível implementação baseada nos recursos do C ++ 11. Ele detecta corretamente a função, mesmo que seja herdada (diferente da solução na resposta aceita, como Mike Kinghan observa em sua resposta ).
A função para a qual esse snippet testa é chamada
serialize
:Uso:
fonte
serialize
próprio aceitar um modelo. Existe uma maneira de testar aserialize
existência sem digitar o tipo exato?A resposta aceita para esta questão da introspecção de função membro de compilação, embora seja bastante popular, tem um problema que pode ser observado no seguinte programa:
Construído com GCC 4.6.3, os resultados do programa
110
- informando-nos queT = std::shared_ptr<int>
não não fornecerint & T::operator*() const
.Se você ainda não é sábio quanto a essa pegadinha, uma olhada na definição de
std::shared_ptr<T>
no cabeçalho<memory>
lançará luz. Nessa implementação,std::shared_ptr<T>
é derivado de uma classe base da qual ela herdaoperator*() const
. Portanto, a instanciação de modeloSFINAE<U, &U::operator*>
que constitui "encontrar" o operadorU = std::shared_ptr<T>
não ocorrerá, porquestd::shared_ptr<T>
não temoperator*()
por si só e a instanciação de modelo não "faz herança".Esse problema não afeta a conhecida abordagem SFINAE, usando "The sizeof () Trick", para detectar apenas se
T
tem alguma função de membromf
(veja, por exemplo, esta resposta e comentários). Mas estabelecer queT::mf
existe muitas vezes (geralmente?) Não é bom o suficiente: você também pode precisar estabelecer que possui a assinatura desejada. É aí que a técnica ilustrada é pontuada. A variante apontada da assinatura desejada é inscrita em um parâmetro de um tipo de modelo que deve ser satisfeito&T::mf
para que o probe SFINAE seja bem-sucedido. Mas essa técnica de instanciação de modelo fornece a resposta errada quandoT::mf
é herdada.Uma técnica SFINAE segura para introspecção de tempo de compilação
T::mf
deve evitar o uso de&T::mf
um argumento de modelo para instanciar um tipo do qual depende a resolução do modelo de função SFINAE. Em vez disso, a resolução da função de modelo SFINAE pode depender apenas de declarações de tipo exatamente pertinentes usadas como tipos de argumento da função de teste SFINAE sobrecarregada.Como resposta à pergunta que cumpre essa restrição, ilustrarei a detecção de compilações de
E T::operator*() const
, arbitráriasT
eE
. O mesmo padrão se aplicará mutatis mutandis para investigar qualquer outra assinatura de método membro.Nesta solução, a função de sonda SFINAE sobrecarregada
test()
é "invocada recursivamente". (É claro que na verdade não é invocado; apenas possui os tipos de retorno de invocações hipotéticas resolvidos pelo compilador.)Precisamos investigar pelo menos um e no máximo dois pontos de informação:
T::operator*()
mesmo? Caso contrário, terminamos.T::operator*()
existe, é a sua assinaturaE T::operator*() const
?Obtemos as respostas avaliando o tipo de retorno de uma única chamada para
test(0,0)
. Isso é feito por:Essa chamada pode ser resolvida com a
/* SFINAE operator-exists :) */
sobrecarga detest()
ou com a/* SFINAE game over :( */
sobrecarga. Não pode resolver a/* SFINAE operator-has-correct-sig :) */
sobrecarga, porque esse espera apenas um argumento e estamos passando dois.Por que estamos passando dois? Simplesmente para forçar a resolução a excluir
/* SFINAE operator-has-correct-sig :) */
. O segundo argumento não tem outro significado.Essa chamada para
test(0,0)
resolverá/* SFINAE operator-exists :) */
apenas no caso de o primeiro argumento 0 satisfazer o primeiro tipo de parâmetro dessa sobrecarga, ou sejadecltype(&A::operator*)
, comA = T
. 0 satisfará esse tipo, casoT::operator*
exista.Vamos supor que o compilador diga Sim para isso. Então, ele continua
/* SFINAE operator-exists :) */
e precisa determinar o tipo de retorno da chamada de função, que nesse caso édecltype(test(&A::operator*))
- o tipo de retorno de mais uma chamada paratest()
.Desta vez, estamos passando apenas um argumento,
&A::operator*
que sabemos que existe, ou não estaríamos aqui. Uma chamada paratest(&A::operator*)
pode resolver para/* SFINAE operator-has-correct-sig :) */
ou novamente para/* SFINAE game over :( */
. A chamada corresponderá/* SFINAE operator-has-correct-sig :) */
caso&A::operator*
satisfaça o tipo de parâmetro único dessa sobrecarga, ou sejaE (A::*)() const
, comA = T
.O compilador dirá Sim aqui se
T::operator*
tiver a assinatura desejada e, em seguida, novamente terá que avaliar o tipo de retorno da sobrecarga. Não há mais "recursões" agora: éstd::true_type
.Se o compilador não escolhe
/* SFINAE operator-exists :) */
para a chamadatest(0,0)
ou não escolher/* SFINAE operator-has-correct-sig :) */
para a chamadatest(&A::operator*)
, em seguida, em ambos os casos ele vai com/* SFINAE game over :( */
e o tipo de retorno final éstd::false_type
.Aqui está um programa de teste que mostra o modelo produzindo as respostas esperadas em uma amostra variada de casos (GCC 4.6.3 novamente).
Existem novas falhas nessa idéia? Pode ser tornado mais genérico sem, mais uma vez, cair no obstáculo que evita?
fonte
Aqui estão alguns trechos de uso: * As entranhas de tudo isso estão mais abaixo
Procure um membro
x
em uma determinada classe. Pode ser var, func, classe, união ou enum:Verifique a função de membro
void x()
:Verifique a variável de membro
x
:Verifique a classe de membro
x
:Verifique a união dos membros
x
:Verifique a enumeração de membro
x
:Verifique qualquer função de membro,
x
independentemente da assinatura:OU
Detalhes e núcleo:
Macros (El Diablo!):
CREATE_MEMBER_CHECK:
CREATE_MEMBER_VAR_CHECK:
CREATE_MEMBER_FUNC_SIG_CHECK:
CREATE_MEMBER_CLASS_CHECK:
CREATE_MEMBER_UNION_CHECK:
CREATE_MEMBER_ENUM_CHECK:
CREATE_MEMBER_FUNC_CHECK:
CREATE_MEMBER_CHECKS:
fonte
Isso deve ser suficiente, se você souber o nome da função de membro que está esperando. (Nesse caso, a função bla falha ao instanciar se não houver função membro (escrever uma que funcione de qualquer maneira é difícil, porque há uma falta de especialização parcial da função. Você pode precisar usar modelos de classe) Além disso, a estrutura de ativação (que é semelhante a enable_if) também pode ser modelado no tipo de função que você deseja que ele tenha como membro.
fonte
Aqui está uma abordagem mais simples da resposta de Mike Kinghan. Isso detectará métodos herdados. Ele também verificará a assinatura exata (diferente da abordagem do jrok, que permite conversões de argumentos).
Exemplo executável
fonte
using
para trazer sobrecargas da classe base. Funciona para mim no MSVC 2015 e com Clang-CL. No entanto, ele não funciona com o MSVC 2012.Você pode usar std :: is_member_function_pointer
fonte
&A::foo
ser um erro de compilação se não háfoo
nada emA
? Eu li a pergunta original como sendo para trabalhar com qualquer classe de entrada, não apenas aquelas que têm algum tipo de membro nomeadofoo
.Eu vim com o mesmo tipo de problema e achei as soluções propostas aqui muito interessantes ... mas tinha o requisito de uma solução que:
Encontrei outro tópico propondo algo assim, com base em uma discussão do BOOST . Aqui está a generalização da solução proposta como uma declaração de duas macros para a classe de características, seguindo o modelo de classes boost :: has_ * .
Essas macros se expandem para uma classe de características com o seguinte protótipo:
Então, qual é o uso típico que se pode fazer com isso?
fonte
Para fazer isso, precisamos usar:
type_traits
cabeçalho, queremos retornar umatrue_type
oufalse_type
de nossas sobrecargastrue_type
sobrecarga que espera umint
e afalse_type
sobrecarga que espera que os parâmetros variáveis explorem: "A prioridade mais baixa da conversão de reticências na resolução de sobrecarga"true_type
função, usaremosdeclval
edecltype
permitiremos detectar a função independentemente das diferenças ou sobrecargas do tipo de retorno entre os métodosVocê pode ver um exemplo ao vivo disso aqui . Mas também vou explicar abaixo:
Eu quero verificar a existência de uma função chamada
test
que leva um tipo conversívelint
, então eu precisaria declarar essas duas funções:decltype(hasTest<a>(0))::value
istrue
(Observe que não há necessidade de criar funcionalidades especiais para lidar com avoid a::test()
sobrecarga, issovoid a::test(int)
é aceito)decltype(hasTest<b>(0))::value
istrue
(Como oint
conversível emdouble
int b::test(double)
é aceito, independentemente do tipo de retorno)decltype(hasTest<c>(0))::value
isfalse
(c
não possui um método nomeadotest
que aceita um tipo conversível a partirint
disso, isso não é aceito)Esta solução tem 2 desvantagens:
test()
método?Portanto, é importante que essas funções sejam declaradas em um namespace de detalhes ou, idealmente, se forem usadas apenas com uma classe, elas devem ser declaradas em particular por essa classe. Para esse fim, escrevi uma macro para ajudá-lo a abstrair essas informações:
Você pode usar isso como:
Chamar posteriormente
details::test_int<a>::value
oudetails::test_void<a>::value
renderiatrue
oufalse
para os fins de código embutido ou metaprogramação.fonte
Para não ser intrusivo, você também pode colocar
serialize
no espaço para nome da classe que está sendo serializada ou da classe de arquivamento, graças à pesquisa Koenig . Consulte Namespaces para substituições gratuitas de funções para obter mais detalhes. :-)Abrir qualquer espaço de nome para implementar uma função livre é Simplesmente Errado. (por exemplo, você não deve abrir um espaço
std
para nome para implementarswap
para seus próprios tipos, mas deve usar a pesquisa Koenig.)fonte
Você parece querer o idioma do detector. As respostas acima são variações sobre isso que funcionam com C ++ 11 ou C ++ 14.
A
std::experimental
biblioteca possui recursos que fazem essencialmente isso. Reformulando um exemplo acima, pode ser:Se você não pode usar std :: experimental, uma versão rudimentar pode ser feita assim:
Como has_serialize_t é realmente std :: true_type ou std :: false_type, ele pode ser usado através de qualquer um dos idiomas comuns do SFINAE:
Ou usando o despacho com resolução de sobrecarga:
fonte
OK. Segunda tentativa. Tudo bem se você também não gosta deste, estou procurando mais idéias.
O artigo de Herb Sutter fala sobre traços. Portanto, você pode ter uma classe de características cuja instanciação padrão tem o comportamento de fallback e, para cada classe em que sua função de membro existe, a classe de características é especializada para chamar a função de membro. Acredito que o artigo de Herb menciona uma técnica para fazer isso, para que não envolva muitas cópias e colagens.
Como eu disse, talvez você não queira o trabalho extra envolvido com as classes de "marcação" que implementam esse membro. Nesse caso, estou procurando uma terceira solução ....
fonte
Sem o suporte ao C ++ 11 (
decltype
), isso pode funcionar:SSCCE
Espero que funcione
A
,Aa
eB
são as classes em questão,Aa
sendo a especial que herda o membro que procuramos.No
FooFinder
otrue_type
efalse_type
são os substitutos para o correspondente C ++ 11 classes. Também para o entendimento da meta programação de modelos, eles revelam a própria base do truque do tamanho da SFINAE.A
TypeSink
é uma estrutura de modelo que é usado mais tarde para afundar o resultado integrante dosizeof
operador para uma instanciação modelo, para formar um tipo.A
match
função é outro tipo de modelo SFINAE que é deixado sem uma contraparte genérica. Portanto, ele só pode ser instanciado se o tipo de argumento corresponder ao tipo para o qual foi especializado.Ambas as
test
funções, juntamente com a declaração enum, finalmente formam o padrão central do SFINAE. Há um genérico usando reticências que retornamfalse_type
e uma contraparte com argumentos mais específicos para ter precedência.Para poder instanciar a
test
função com um argumento de modelo deT
, amatch
função deve ser instanciada, pois seu tipo de retorno é necessário para instanciar oTypeSink
argumento. A ressalva é que&U::foo
, sendo envolvido em um argumento de função, não é referido a partir de uma especialização de argumento de modelo, portanto a pesquisa de membro herdada ainda ocorre.fonte
Se você estiver usando a loucura do Facebook, a macro está pronta para ajudá-lo:
Embora os detalhes da implementação sejam os mesmos da resposta anterior, o uso de uma biblioteca é mais simples.
fonte
Eu tive uma necessidade semelhante e me deparei com este SO. Existem muitas soluções interessantes / poderosas propostas aqui, embora seja um pouco longo apenas para uma necessidade específica: detectar se uma classe tem função de membro com uma assinatura precisa. Então, eu fiz algumas leituras / testes e criei minha versão que poderia ser interessante. Detecta:
com uma assinatura precisa. Como não preciso capturar nenhuma assinatura (isso exigiria uma solução mais complicada), essa é a minha suíte. Ele basicamente usou o enable_if_t .
Resultado :
fonte
Com base JROK 's resposta , eu ter evitado o uso de classes e / ou funções modelo aninhado.
Podemos usar as macros acima como abaixo:
Sugestões são bem vindas.
fonte