É possível escrever um modelo que altera o comportamento, dependendo se uma determinada função de membro é definida em uma classe?
Aqui está um exemplo simples do que eu gostaria de escrever:
template<class T>
std::string optionalToString(T* obj)
{
if (FUNCTION_EXISTS(T->toString))
return obj->toString();
else
return "toString not defined";
}
Então, se class T
tenha toString()
definido, então ele usa-lo; caso contrário, não. A parte mágica que eu não sei fazer é a parte "FUNCTION_EXISTS".
Respostas:
Sim, com o SFINAE, você pode verificar se uma determinada classe fornece um determinado método. Aqui está o código de trabalho:
Acabei de testar com Linux e gcc 4.1 / 4.3. Não sei se é portátil para outras plataformas executando diferentes compiladores.
fonte
typeof
pordecltype
quando se utiliza C ++ 0x , por exemplo, via -std = c ++ 0x.Essa questão é antiga, mas com o C ++ 11, conseguimos uma nova maneira de verificar a existência de uma função (ou a existência de qualquer membro que não seja do tipo), dependendo do SFINAE novamente:
Agora vamos a algumas explicações. Primeira coisa, eu uso a expressão SFINAE para excluir as
serialize(_imp)
funções da resolução de sobrecarga, se a primeira expressão dentrodecltype
não for válida (ou seja, a função não existe).O
void()
é usado para criar o tipo de retorno de todas essas funçõesvoid
.O
0
argumento é usado para preferir aos << obj
sobrecarga se ambos estiverem disponíveis (literal0
é do tipoint
e, como tal, a primeira sobrecarga é uma correspondência melhor).Agora, você provavelmente deseja que uma característica verifique se existe uma função. Felizmente, é fácil escrever isso. Note, porém, que você precisa escrever um traço de si mesmo para cada nome de função diferente, você pode querer.
Exemplo ao vivo.
E para explicações. Primeiro,
sfinae_true
é do tipo auxiliar, e basicamente equivale ao mesmo que escreverdecltype(void(std::declval<T>().stream(a0)), std::true_type{})
. A vantagem é simplesmente que é mais curto.Em seguida, o
struct has_stream : decltype(...)
herda de umstd::true_type
oustd::false_type
no final, dependendo se odecltype
check-intest_stream
falha ou não.Por último,
std::declval
fornece um "valor" de qualquer tipo que você passe, sem precisar saber como construí-lo. Observe que isso só é possível dentro de um contexto não avaliado, comodecltype
,sizeof
e outros.Observe que isso
decltype
não é necessariamente necessário, poissizeof
(e todos os contextos não avaliados) obtiveram esse aprimoramento. É quedecltype
já oferece um tipo e, como tal, é mais limpo. Aqui está umasizeof
versão de uma das sobrecargas:Os parâmetros
int
elong
ainda estão lá pelo mesmo motivo. O ponteiro da matriz é usado para fornecer um contexto ondesizeof
pode ser usado.fonte
decltype
oversizeof
também é que um temporário não é introduzido por regras especialmente criadas para chamadas de função (portanto, você não precisa ter direitos de acesso ao destruidor do tipo de retorno e não causará uma instancia implícita se o tipo de retorno for uma instanciação de modelo de classe).static_assert(has_stream<X, char>() == true, "fail X");
será compilado e não declarado, porque char é convertível em int; portanto, se esse comportamento não for desejado e desejar que todos os tipos de argumentos correspondam, não sei como isso pode ser alcançado.O C ++ permite que o SFINAE seja usado para isso (observe que, com os recursos do C ++ 11, isso é mais simples, pois suporta SFINAE estendido em expressões quase arbitrárias - o abaixo foi criado para trabalhar com compiladores comuns do C ++ 03):
o modelo e a macro acima tentam instanciar um modelo, fornecendo a ele um tipo de ponteiro de função de membro e o ponteiro de função de membro real. Se os tipos não se ajustarem, o SFINAE fará com que o modelo seja ignorado. Uso como este:
Mas observe que você não pode simplesmente chamar essa
toString
função nesse ramo se. como o compilador verificará a validade nos dois ramos, isso falharia nos casos em que a função não existe. Uma maneira é usar o SFINAE mais uma vez (enable_if também pode ser obtido do boost):Divirta-se usando. A vantagem disso é que ele também funciona para funções membro sobrecarregadas e também para funções membro const (lembre-se de usar
std::string(T::*)() const
o tipo de ponteiro da função membro!).fonte
type_check
é usado para garantir que as assinaturas estejam de acordo exatamente. Existe uma maneira de fazer com que ele corresponda a qualquer método que possa ser chamado da maneira que um método com assinaturaSign
pode ser chamado? (Por exemplo, seSign
=std::string(T::*)()
, permitirstd::string T::toString(int default = 42, ...)
a combinar.)T
não deve ser um tipo primitivo, porque a declaração do ponteiro para o método-T não está sujeita à SFINAE e causará erro para qualquer T. não pertencente à classe. IMO, a solução mais fácil é combinar com ais_class
verificação de impulso.toString
for uma função de modelo?C ++ 20 -
requires
expressõesCom o C ++ 20, vêm conceitos e ferramentas variadas, como
requires
expressões que são uma maneira interna de verificar a existência de uma função. Com eles, você pode reescrever suaoptionalToString
função da seguinte maneira:Pré-C ++ 20 - Kit de ferramentas de detecção
N4502 propõe um kit de ferramentas de detecção para inclusão na biblioteca padrão do C ++ 17 que, eventualmente, foi incluída nos fundamentos da biblioteca TS v2. Provavelmente, ele nunca entrará no padrão porque foi incluído nas
requires
expressões desde então, mas ainda resolve o problema de uma maneira um tanto elegante. O kit de ferramentas apresenta algumas metafunções, incluindo asstd::is_detected
que podem ser usadas para gravar facilmente metafunções de detecção de tipo ou função na parte superior. Aqui está como você pode usá-lo:Observe que o exemplo acima não foi testado. O kit de ferramentas de detecção ainda não está disponível nas bibliotecas padrão, mas a proposta contém uma implementação completa que você pode copiar facilmente se realmente precisar. Ele funciona bem com o recurso C ++ 17
if constexpr
:C ++ 14 - Boost.Hana
O Boost.Hana aparentemente se baseia nesse exemplo específico e fornece uma solução para C ++ 14 em sua documentação, então vou citá-lo diretamente:
Boost.TTI
Outro kit de ferramentas um tanto idiomático para executar essa verificação - ainda que menos elegante - é o Boost.TTI , introduzido no Boost 1.54.0. Para o seu exemplo, você precisaria usar a macro
BOOST_TTI_HAS_MEMBER_FUNCTION
. Aqui está como você pode usá-lo:Em seguida, você pode usar o
bool
para criar uma verificação SFINAE.Explicação
A macro
BOOST_TTI_HAS_MEMBER_FUNCTION
gera a metafunçãohas_member_function_toString
que leva o tipo verificado como seu primeiro parâmetro de modelo. O segundo parâmetro do modelo corresponde ao tipo de retorno da função membro e os seguintes parâmetros correspondem aos tipos dos parâmetros da função. O membrovalue
contémtrue
se a classeT
tem uma função de membrostd::string toString()
.Como alternativa,
has_member_function_toString
pode usar um ponteiro de função de membro como um parâmetro de modelo. Portanto, é possível substituirhas_member_function_toString<T, std::string>::value
porhas_member_function_toString<std::string T::* ()>::value
.fonte
Embora essa pergunta tenha dois anos, ouso acrescentar minha resposta. Espero que esclareça a solução anterior, indiscutivelmente excelente. Peguei as respostas muito úteis de Nicola Bonelli e Johannes Schaub e as fundi em uma solução que é, IMHO, mais legível, clara e que não requer a
typeof
extensão:Eu verifiquei com o gcc 4.1.2. O crédito é principalmente para Nicola Bonelli e Johannes Schaub; portanto, vote-os se minha resposta o ajudar :)
fonte
toString
. Se você escreve uma biblioteca genérica, que deseja trabalhar com qualquer classe existente (pense em algo como impulso), exigir que o usuário defina especializações adicionais de alguns modelos obscuros pode ser inaceitável. Às vezes, é preferível escrever um código muito complicado para manter a interface pública o mais simples possível.Uma solução simples para C ++ 11:
Atualização, 3 anos depois: (e isso não foi testado). Para testar a existência, acho que isso funcionará:
fonte
template<typename>
a sobrecarga variável: não estava sendo considerado para resolução.É para isso que existem características de tipo. Infelizmente, eles precisam ser definidos manualmente. No seu caso, imagine o seguinte:
fonte
&T::x
ou implicitamente, vinculando-o a uma referência).type traits
a compilação condicional em C ++ 11Bem, essa pergunta já tem uma longa lista de respostas, mas eu gostaria de enfatizar o comentário de Morwenn: existe uma proposta para o C ++ 17 que o torna muito mais simples. Veja N4502 para obter detalhes, mas como um exemplo independente, considere o seguinte.
Esta parte é a parte constante, coloque-a em um cabeçalho.
depois, há a parte variável, onde você especifica o que procura (um tipo, um tipo de membro, uma função, uma função de membro etc.). No caso do PO:
O exemplo a seguir, retirado do N4502 , mostra uma análise mais elaborada:
Comparado às outras implementações descritas acima, essa é bastante simples: basta um conjunto reduzido de ferramentas (
void_t
edetect
), sem necessidade de macros peludas. Além disso, foi relatado (ver N4502 ) que é mensuravelmente mais eficiente (tempo de compilação e consumo de memória do compilador) do que as abordagens anteriores.Aqui está um exemplo ao vivo . Funciona bem com o Clang, mas, infelizmente, as versões do GCC anteriores à 5.1 seguiam uma interpretação diferente do padrão C ++ 11, que fazia
void_t
com que não funcionasse conforme o esperado. Yakk já forneceu a solução alternativa: use a seguinte definição devoid_t
( void_t na lista de parâmetros funciona, mas não como tipo de retorno ):fonte
Esta é uma solução C ++ 11 para o problema geral se "Se eu fizesse o X, ele seria compilado?"
Traço
has_to_string
quehas_to_string<T>::value
étrue
se e somente seT
tem um método.toString
que pode ser chamado com 0 argumentos neste contexto.Em seguida, eu usaria o envio de tags:
que tende a ser mais sustentável do que expressões SFINAE complexas.
Você pode escrever esses traços com uma macro se você o fizer muito, mas eles são relativamente simples (algumas linhas cada), por isso talvez não valha a pena:
o que o acima faz é criar uma macro
MAKE_CODE_TRAIT
. Você passa o nome da característica que deseja e algum código que pode testar o tipoT
. Portanto:cria a classe de características acima.
Como um aparte, a técnica acima faz parte do que a MS chama de "expressão SFINAE", e seu compilador de 2013 falha bastante.
Observe que no C ++ 1y é possível a seguinte sintaxe:
que é um ramo condicional da compilação em linha que abusa de muitos recursos do C ++. Fazer isso provavelmente não vale a pena, pois o benefício (do código estar embutido) não vale o custo (quase ninguém entende como ele funciona), mas a existência dessa solução acima pode ser interessante.
fonte
has_to_string
no entanto.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
sig_check<func_sig, &T::func_name>
para a verificação de função livre:sig_check<func_sig, &func_name>
ela falha ao criar com "identificador não declarado" mencionando o nome da função que queremos verificar? porque eu esperaria que o SFINAE cometesse NÃO um erro, isso é apenas para membros, por que não para funções livres?Eu escrevi uma resposta para isso em outro tópico que (ao contrário das soluções acima) também verifica as funções-membro herdadas:
SFINAE para verificar funções-membro herdadas
Aqui estão alguns exemplos dessa solução:
Exemplo 1:
Estamos procurando um membro com a seguinte assinatura:
T::const_iterator begin() const
Observe que ele verifica a consistência do método e também funciona com tipos primitivos. (Quero dizer
has_const_begin<int>::value
é falso e não causa um erro em tempo de compilação.)Exemplo 2
Agora estamos procurando a assinatura:
void foo(MyClass&, unsigned)
Observe que o MyClass não precisa ser construtível por padrão ou para satisfazer qualquer conceito especial. A técnica também funciona com membros do modelo.
Aguardo ansiosamente opiniões sobre isso.
fonte
Agora isso foi um bom quebra-cabeça - ótima pergunta!
Aqui está uma alternativa à solução de Nicola Bonelli que não depende do
typeof
operador não padrão .Infelizmente, ele não funciona no GCC (MinGW) 3.4.5 ou no Digital Mars 8.42n, mas funciona em todas as versões do MSVC (incluindo VC6) e no Comeau C ++.
O bloco de comentários mais longo tem os detalhes de como ele funciona (ou deve funcionar). Como está escrito, não tenho certeza de qual comportamento é compatível com os padrões - gostaria de comentar sobre isso.
atualização - 7 de novembro de 2008:
Parece que enquanto esse código está sintaticamente correto, o comportamento que o MSVC e o Comeau C ++ mostram não segue o padrão (obrigado a Leon Timmermans e litb por me apontarem na direção certa). O padrão C ++ 03 diz o seguinte:
Portanto, parece que quando o MSVC ou o Comeau consideram a
toString()
função de membro deT
executar a pesquisa de nome no site de chamada emdoToString()
quando o modelo é instanciado, isso está incorreto (mesmo que seja o comportamento que eu estava procurando nesse caso).O comportamento do GCC e do Digital Mars parece estar correto - em ambos os casos, a
toString()
função de não-membro está vinculada à chamada.Ratos - Eu pensei que poderia ter encontrado uma solução inteligente; em vez disso, descobri alguns erros do compilador ...
fonte
A solução C ++ padrão apresentada aqui pelo litb não funcionará conforme o esperado se o método for definido em uma classe base.
Para uma solução que lida com essa situação, consulte:
Em russo: http://www.rsdn.ru/forum/message/2759773.1.aspx
Tradução em inglês por Roman.Perepelitsa: http://groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?pli=1
É insanamente inteligente. No entanto, um problema com essa solução é fornecer erros do compilador se o tipo que está sendo testado não puder ser usado como classe base (por exemplo, tipos primitivos)
No Visual Studio, notei que, se estiver trabalhando com o método sem argumentos, um par extra de redundante () precisará ser inserido nos argumentos para deduzir () no tamanho da expressão.
fonte
struct g { void f(); private: void f(int); };
porque uma das funções é privada (isso ocorre porque o código fazusing g::f;
, o que faz com que falhe se algumaf
não estiver acessível).O MSVC possui as palavras-chave __if_exists e __if_not_exists ( Doc ). Juntamente com a abordagem tipo-SFINAE de Nicola, eu poderia criar uma verificação para o GCC e o MSVC, como o OP procurava.
Atualização: A fonte pode ser encontrada aqui
fonte
Um exemplo usando SFINAE e especialização parcial de modelo, escrevendo uma
Has_foo
verificação de conceito:fonte
Modifiquei a solução fornecida em https://stackoverflow.com/a/264088/2712152 para torná-la um pouco mais geral. Além disso, como ele não usa nenhum dos novos recursos do C ++ 11, podemos usá-lo com compiladores antigos e também devemos trabalhar com o msvc. Mas os compiladores devem permitir que o C99 use isso, pois ele usa macros variadas.
A macro a seguir pode ser usada para verificar se uma classe específica tem um typedef específico ou não.
A macro a seguir pode ser usada para verificar se uma classe específica tem uma função de membro específica ou não com um número determinado de argumentos.
Podemos usar as 2 macros acima para executar as verificações de has_typedef e has_mem_func como:
fonte
HAS_MEM_FUNC( onNext, has_memberfn_onNext, void, Args... );
...template <typename V> struct Foo { void onNext(const V &); static_assert< has_memberfn_onNext<Foo<V>,const V &>::value, "API fail" ); };
Ninguém estranho sugeriu o seguinte truque legal que vi uma vez neste site:
Você precisa se certificar de que T é uma classe. Parece que a ambiguidade na pesquisa de foo é uma falha de substituição. Eu fiz funcionar no gcc, não tenho certeza se é padrão.
fonte
O modelo genérico que pode ser usado para verificar se algum "recurso" é suportado pelo tipo:
O modelo que verifica se existe um método
foo
compatível com a assinaturadouble(const char*)
Exemplos
http://coliru.stacked-crooked.com/a/83c6a631ed42cea4
fonte
has_foo
na chamada de modelo deis_supported
. O que eu gostaria é chamar algo como:std::cout << is_supported<magic.foo(), struct1>::value << std::endl;
. A razão para isso, quero definir umhas_foo
para cada assinatura de função diferente que desejo verificar antes de poder verificar a função?Que tal esta solução?
fonte
toString
estiver sobrecarregado, como&U::toString
é ambíguo.Há muitas respostas aqui, mas não consegui encontrar uma versão que execute a ordenação real da resolução do método, sem usar nenhum dos recursos mais recentes do c ++ (apenas os recursos do c ++ 98).
Nota: Esta versão foi testada e funciona com vc ++ 2013, g ++ 5.2.0 e o compilador onlline.
Então, eu vim com uma versão que usa apenas sizeof ():
Demonstração ao vivo (com verificação estendida do tipo de retorno e solução alternativa do vc ++ 2010): http://cpp.sh/5b2vs
Nenhuma fonte, como eu mesmo a criei.
Ao executar a demonstração ao vivo no compilador g ++, observe que tamanhos de matriz 0 são permitidos, o que significa que o static_assert usado não acionará um erro do compilador, mesmo quando falhar.
Uma solução alternativa comumente usada é substituir o 'typedef' na macro por 'extern'.
fonte
static_assert(false);
). Eu estava usando isso em conexão com o CRTP, onde quero determinar se a classe derivada tem uma função específica - que acaba não funcionando, mas suas declarações sempre são aprovadas. Perdi um pouco de cabelo com esse.Aqui está minha versão que lida com todas as possíveis sobrecargas de função de membro com aridade arbitrária, incluindo funções de membro de modelo, possivelmente com argumentos padrão. Ele distingue 3 cenários mutuamente exclusivos ao fazer uma chamada de função de membro para algum tipo de classe, com os seguintes tipos de argumentos: (1) válido ou (2) ambíguo ou (3) inviável. Exemplo de uso:
Agora você pode usá-lo assim:
Aqui está o código, escrito em c ++ 11, no entanto, você pode facilmente portá-lo (com pequenos ajustes) para não-c ++ 11 que tenha extensões de tipo (por exemplo, gcc). Você pode substituir a macro HAS_MEM pela sua.
fonte
Você pode pular toda a metaprogramação em C ++ 14 e apenas escrever isso usando
fit::conditional
a biblioteca Fit :Você também pode criar a função diretamente das lambdas:
No entanto, se você estiver usando um compilador que não suporta lambdas genéricos, precisará escrever objetos de função separados:
fonte
fit
nenhuma biblioteca que não seja a padrão?Com o C ++ 20, você pode escrever o seguinte:
fonte
Aqui está um exemplo do código de trabalho.
toStringFn<T>* = nullptr
habilitará a função que recebe umint
argumento extra que tem uma prioridade sobre a função que recebelong
quando chamada com0
.Você pode usar o mesmo princípio para as funções que retornam
true
se a função for implementada.fonte
Eu tive um problema parecido:
Uma classe de modelo que pode ser derivada de poucas classes base, algumas que possuem um determinado membro e outras que não.
Resolvi-o de maneira semelhante à resposta "typeof" (de Nicola Bonelli), mas com o decltype, ele compila e executa corretamente no MSVS:
fonte
Mais uma maneira de fazer isso no C ++ 17 (inspirado no boost: hana).
Escreva uma vez e use várias vezes. Não requer
has_something<T>
classes de características de tipo.Exemplo
fonte
fonte