Verificação por modelo da existência de uma função de membro da classe?


498

É 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 Ttenha 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".


6
É claro que nem é preciso dizer que as respostas do modelo abaixo funcionam apenas com informações em tempo de compilação, ou seja, T deve ter toString. Se você passar em uma subclasse de T que faz definir toString, mas T faz não , você será informado toString não está definido.
Alice Purcell

Respostas:


319

Sim, com o SFINAE, você pode verificar se uma determinada classe fornece um determinado método. Aqui está o código de trabalho:

#include <iostream>

struct Hello
{
    int helloworld() { return 0; }
};

struct Generic {};    

// SFINAE test
template <typename T>
class has_helloworld
{
    typedef char one;
    struct two { char x[2]; };

    template <typename C> static one test( typeof(&C::helloworld) ) ;
    template <typename C> static two test(...);    

public:
    enum { value = sizeof(test<T>(0)) == sizeof(char) };
};

int main(int argc, char *argv[])
{
    std::cout << has_helloworld<Hello>::value << std::endl;
    std::cout << has_helloworld<Generic>::value << std::endl;
    return 0;
}

Acabei de testar com Linux e gcc 4.1 / 4.3. Não sei se é portátil para outras plataformas executando diferentes compiladores.


18
Embora, usei o seguinte para 'one' e 'two': typedef char Small; classe Big {char dummy [2];} para garantir nenhuma ambiguidade sobre o tamanho da variável dependente da plataforma.
user23167

6
Eu duvido que ela existe na Terra uma plataforma com o sizeof (char) == sizeof (long)
Nicola Bonelli

17
Não tenho muita certeza, mas não acho que seja portátil. typeof é uma extensão do GCC, isso não funcionará em outros compiladores.
9118 Leon Timmermans #

56
typeof não é necessário - char [sizeof (& C :: helloworld)] também funciona. E para evitar sizeof (long) == sizeof (char), use uma struct {char [2]} ;. Ele deve ter um tamanho> = 2
MSalters 02/02

57
Trivial, mas levei um tempo para descobrir: substituir typeofpor decltypequando se utiliza C ++ 0x , por exemplo, via -std = c ++ 0x.
HRR

264

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:

template<class T>
auto serialize_imp(std::ostream& os, T const& obj, int)
    -> decltype(os << obj, void())
{
  os << obj;
}

template<class T>
auto serialize_imp(std::ostream& os, T const& obj, long)
    -> decltype(obj.stream(os), void())
{
  obj.stream(os);
}

template<class T>
auto serialize(std::ostream& os, T const& obj)
    -> decltype(serialize_imp(os, obj, 0), void())
{
  serialize_imp(os, obj, 0);
}

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 0argumento é usado para preferir a os << objsobrecarga se ambos estiverem disponíveis (literal 0é do tipo inte, 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.

#include <type_traits>

template<class>
struct sfinae_true : std::true_type{};

namespace detail{
  template<class T, class A0>
  static auto test_stream(int)
      -> sfinae_true<decltype(std::declval<T>().stream(std::declval<A0>()))>;
  template<class, class A0>
  static auto test_stream(long) -> std::false_type;
} // detail::

template<class T, class Arg>
struct has_stream : decltype(detail::test_stream<T, Arg>(0)){};

Exemplo ao vivo.

E para explicações. Primeiro, sfinae_trueé do tipo auxiliar, e basicamente equivale ao mesmo que escrever decltype(void(std::declval<T>().stream(a0)), std::true_type{}). A vantagem é simplesmente que é mais curto.
Em seguida, o struct has_stream : decltype(...)herda de um std::true_typeou std::false_typeno final, dependendo se o decltypecheck-in test_streamfalha ou não.
Por último, std::declvalfornece 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, como decltype, sizeofe outros.


Observe que isso decltypenão é necessariamente necessário, pois sizeof(e todos os contextos não avaliados) obtiveram esse aprimoramento. É que decltypejá oferece um tipo e, como tal, é mais limpo. Aqui está uma sizeofversão de uma das sobrecargas:

template<class T>
void serialize_imp(std::ostream& os, T const& obj, int,
    int(*)[sizeof((os << obj),0)] = 0)
{
  os << obj;
}

Os parâmetros inte longainda estão lá pelo mesmo motivo. O ponteiro da matriz é usado para fornecer um contexto onde sizeofpode ser usado.


4
A vantagem do decltypeover sizeoftambé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).
Johannes Schaub - litb

5
A Microsoft ainda não implementou o Expression SFINAE em seu compilador C ++. Imagine que eu poderia ajudar a economizar tempo para algumas pessoas, pois estava confuso por que isso não estava funcionando para mim. Solução agradável, porém, mal posso esperar para usá-lo no Visual Studio!
Jonathan

3
Seu primeiro elo exemplo é quebrado
NathanOliver

1
Deve-se dizer que isso 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.
Gabriel

4
Se você está tão intrigado quanto eu com os dois argumentos para decltype: decltype leva apenas um; a vírgula é um operador aqui. Veja stackoverflow.com/questions/16044514/…
André

159

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):

#define HAS_MEM_FUNC(func, name)                                        \
    template<typename T, typename Sign>                                 \
    struct name {                                                       \
        typedef char yes[1];                                            \
        typedef char no [2];                                            \
        template <typename U, U> struct type_check;                     \
        template <typename _1> static yes &chk(type_check<Sign, &_1::func > *); \
        template <typename   > static no  &chk(...);                    \
        static bool const value = sizeof(chk<T>(0)) == sizeof(yes);     \
    }

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:

HAS_MEM_FUNC(toString, has_to_string);

template<typename T> void
doSomething() {
   if(has_to_string<T, std::string(T::*)()>::value) {
      ...
   } else {
      ...
   }
}

Mas observe que você não pode simplesmente chamar essa toStringfunçã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):

template<bool C, typename T = void>
struct enable_if {
  typedef T type;
};

template<typename T>
struct enable_if<false, T> { };

HAS_MEM_FUNC(toString, has_to_string);

template<typename T> 
typename enable_if<has_to_string<T, 
                   std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
   /* something when T has toString ... */
   return t->toString();
}

template<typename T> 
typename enable_if<!has_to_string<T, 
                   std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
   /* something when T doesnt have toString ... */
   return "T::toString() does not exist.";
}

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::*)() consto tipo de ponteiro da função membro!).


7
Gosto de como 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 assinatura Signpode ser chamado? (Por exemplo, se Sign= std::string(T::*)(), permitir std::string T::toString(int default = 42, ...)a combinar.)
j_random_hacker

5
Eu apenas descobri algo sobre isso que não era imediatamente óbvio para mim, portanto, caso ajude os outros: chk não é e não precisa ser definido! O operador sizeof determina o tamanho da saída de chk sem que chk precise ser chamado.
SCFrench

3
@ deek0146: Sim, Tnã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 a is_classverificação de impulso.
Jan Hudec

2
Como posso fazer isso funcionar se toStringfor uma função de modelo?
Frank

4
Isso é (ou algo equivalente) no Boost?
Dan Nissenbaum 29/03

89

C ++ 20 - requiresexpressões

Com o C ++ 20, vêm conceitos e ferramentas variadas, como requiresexpressões que são uma maneira interna de verificar a existência de uma função. Com eles, você pode reescrever sua optionalToStringfunção da seguinte maneira:

template<class T>
std::string optionalToString(T* obj)
{
    constexpr bool has_toString = requires(const T& t) {
        t.toString();
    };

    if constexpr (has_toString)
        return obj->toString();
    else
        return "toString not defined";
}

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 requiresexpressões desde então, mas ainda resolve o problema de uma maneira um tanto elegante. O kit de ferramentas apresenta algumas metafunções, incluindo as std::is_detectedque 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:

template<typename T>
using toString_t = decltype( std::declval<T&>().toString() );

template<typename T>
constexpr bool has_toString = std::is_detected_v<toString_t, T>;

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:

template<class T>
std::string optionalToString(T* obj)
{
    if constexpr (has_toString<T>)
        return obj->toString();
    else
        return "toString not defined";
}

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:

[...] Hana fornece uma is_validfunção que pode ser combinada com lambdas genéricas C ++ 14 para obter uma implementação muito mais limpa da mesma coisa:

auto has_toString = hana::is_valid([](auto&& obj) -> decltype(obj.toString()) { });

Isso nos deixa com um objeto de função has_toStringque retorna se a expressão fornecida é válida no argumento que passamos para ela. O resultado é retornado como um IntegralConstant, portanto, não é um problema aqui porque o resultado da função é representado como um tipo de qualquer maneira. Agora, além de ser menos detalhada (essa é uma linha!), A intenção é muito mais clara. Outros benefícios são o fato de que has_toStringpodem ser passados ​​para algoritmos de ordem superior e também podem ser definidos no escopo da função, portanto, não há necessidade de poluir o escopo do espaço para nome com detalhes de implementação.

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:

#include <boost/tti/has_member_function.hpp>

// Generate the metafunction
BOOST_TTI_HAS_MEMBER_FUNCTION(toString)

// Check whether T has a member function toString
// which takes no parameter and returns a std::string
constexpr bool foo = has_member_function_toString<T, std::string>::value;

Em seguida, você pode usar o boolpara criar uma verificação SFINAE.

Explicação

A macro BOOST_TTI_HAS_MEMBER_FUNCTIONgera a metafunção has_member_function_toStringque 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 membro valuecontém truese a classe Ttem uma função de membro std::string toString().

Como alternativa, has_member_function_toStringpode usar um ponteiro de função de membro como um parâmetro de modelo. Portanto, é possível substituir has_member_function_toString<T, std::string>::valuepor has_member_function_toString<std::string T::* ()>::value.


1
mais conciso do que 03
ZFY

@ZFY Acho que o Boost.TTI também funciona com C ++ 03, mas é a solução menos elegante do lote.
Morwenn 13/03

A solução C ++ 20 é realmente válida? Eu gostaria - mas é recusado pelo g ++ e pelo msvc - aceito apenas pelo clang.
Bernd Baumanns 04/04

Na cppreference, você pode ler: Se uma expressão de requerimento contiver tipos ou expressões inválidas em seus requisitos, e não aparecer na declaração de uma entidade modelada, o programa será mal formado.
Bernd Baumanns 04/04

@BerndBaumanns Sério? Consegui fazê-lo funcionar com o tronco do GCC: godbolt.org/z/CBwZdE Talvez você esteja certo, verifiquei apenas se funcionava, mas não verifiquei se era legal de acordo com a redação padrão.
Morwenn 5/04

56

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 typeofextensão:

template <class Type>
class TypeHasToString
{
    // This type won't compile if the second template parameter isn't of type T,
    // so I can put a function pointer type in the first parameter and the function
    // itself in the second thus checking that the function has a specific signature.
    template <typename T, T> struct TypeCheck;

    typedef char Yes;
    typedef long No;

    // A helper struct to hold the declaration of the function pointer.
    // Change it if the function signature changes.
    template <typename T> struct ToString
    {
        typedef void (T::*fptr)();
    };

    template <typename T> static Yes HasToString(TypeCheck< typename ToString<T>::fptr, &T::toString >*);
    template <typename T> static No  HasToString(...);

public:
    static bool const value = (sizeof(HasToString<Type>(0)) == sizeof(Yes));
};

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 :)


1
Apenas imaginando, isso faz alguma coisa que a solução de Konrad Rudolph abaixo não faz?
Alastair Irvine

3
@AlastairIrvine, esta solução oculta toda a lógica interna, o Konrad's coloca parte do fardo para o usuário. Embora curta e muito mais legível, a solução do Konrad requer uma especialização de modelo separada para cada classe que possui 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.
FireAphis

30

Uma solução simples para C ++ 11:

template<class T>
auto optionalToString(T* obj)
 -> decltype(  obj->toString()  )
{
    return     obj->toString();
}
auto optionalToString(...) -> string
{
    return "toString not defined";
}

Atualização, 3 anos depois: (e isso não foi testado). Para testar a existência, acho que isso funcionará:

template<class T>
constexpr auto test_has_toString_method(T* obj)
 -> decltype(  obj->toString() , std::true_type{} )
{
    return     obj->toString();
}
constexpr auto test_has_toString_method(...) -> std::false_type
{
    return "toString not defined";
}

4
Isso é simples e elegante, mas, estritamente falando, não responde à pergunta do OP: você não permite que o chamador verifique a existência de uma função, sempre a fornece . Mas bom mesmo assim.
Adrian W

@AdrianW, bom ponto. Eu atualizei minha resposta. Eu não testei embora
Aaron McDaid

Caso isso ajude outra pessoa, eu não poderia fazer isso funcionar sem template<typename>a sobrecarga variável: não estava sendo considerado para resolução.
Laboratorio Cobotica

Novamente, isso é C ++ 11 inválido.
Peter

29

É para isso que existem características de tipo. Infelizmente, eles precisam ser definidos manualmente. No seu caso, imagine o seguinte:

template <typename T>
struct response_trait {
    static bool const has_tostring = false;
};

template <>
struct response_trait<your_type_with_tostring> {
    static bool const has_tostring = true;
}

5
você deve preferir enum para características em vez de constantes estáticas: "Membros constantes estáticos são lvalues, o que força o compilador a instanciar e alocar a definição para o membro estático. Como resultado, o cálculo não está mais limitado a um tempo de compilação puro" "efeito".
Özgür

5
"Os valores de enumeração não são lvalues ​​(ou seja, eles não têm um endereço). Então, quando você os passa" por referência ", nenhuma memória estática é usada. É quase exatamente como se você passasse o valor calculado como um literal Essas considerações nos motivam a usar valores de enumeração "Templates C ++: The Complete Guide
20/09

22
Controle: não, a passagem citada não se aplica aqui, pois constantes estáticas do tipo inteiro são um caso especial! Eles se comportam exatamente como um enum aqui e são a maneira preferida. O antigo enum hack era necessário apenas em compiladores que não seguiam o padrão C ++.
23909 Konrad Rudolph

3
@ Roger Pate: Não é bem assim. "Usado no programa" aqui é aparentemente sinônimo de "referenciado". A leitura predominante desta passagem, e a implementada por todos os compiladores C ++ modernos, é que você pode pegar o valor de uma constante estática sem precisar declará-la (a frase anterior diz o seguinte: “… o membro pode aparecer em expressões constantes integrais … ”). Você precisa defini-lo se usar seu endereço (explicitamente, via &T::xou implicitamente, vinculando-o a uma referência).
Konrad Rudolph


25

Bem, 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.

// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;

// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};

// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};

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:

template <typename T>
using toString_t = decltype(std::declval<T>().toString());

template <typename T>
using has_toString = detect<T, toString_t>;

O exemplo a seguir, retirado do N4502 , mostra uma análise mais elaborada:

// Archetypal expression for assignment operation.
template <typename T>
using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())

// Trait corresponding to that archetype.
template <typename T>
using is_assignable = detect<T, assign_t>;

Comparado às outras implementações descritas acima, essa é bastante simples: basta um conjunto reduzido de ferramentas ( void_te detect), 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_tcom que não funcionasse conforme o esperado. Yakk já forneceu a solução alternativa: use a seguinte definição de void_t( void_t na lista de parâmetros funciona, mas não como tipo de retorno ):

#if __GNUC__ < 5 && ! defined __clang__
// https://stackoverflow.com/a/28967049/1353549
template <typename...>
struct voider
{
  using type = void;
};
template <typename...Ts>
using void_t = typename voider<Ts...>::type;
#else
template <typename...>
using void_t = void;
#endif

É possível estendê-lo para detectar funções que não são membros?
plasmacel

Sim claro. Observe atentamente os exemplos: você basicamente fornece uma expressão e verifica se é válida. Nada requer que essa expressão seja apenas sobre uma chamada de função de membro.
akim

O N4502 ( open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf ) é o caminho do futuro ... Eu estava procurando uma maneira clara de detectar coisas nos tipos e o N4502 é o caminho ir.
tlonuk

11

Esta é uma solução C ++ 11 para o problema geral se "Se eu fizesse o X, ele seria compilado?"

template<class> struct type_sink { typedef void type; }; // consumes a type, and makes it `void`
template<class T> using type_sink_t = typename type_sink<T>::type;
template<class T, class=void> struct has_to_string : std::false_type {}; \
template<class T> struct has_to_string<
  T,
  type_sink_t< decltype( std::declval<T>().toString() ) >
>: std::true_type {};

Traço has_to_stringque has_to_string<T>::valueé truese e somente se Ttem um método .toStringque pode ser chamado com 0 argumentos neste contexto.

Em seguida, eu usaria o envio de tags:

namespace details {
  template<class T>
  std::string optionalToString_helper(T* obj, std::true_type /*has_to_string*/) {
    return obj->toString();
  }
  template<class T>
  std::string optionalToString_helper(T* obj, std::false_type /*has_to_string*/) {
    return "toString not defined";
  }
}
template<class T>
std::string optionalToString(T* obj) {
  return details::optionalToString_helper( obj, has_to_string<T>{} );
}

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:

#define MAKE_CODE_TRAIT( TRAIT_NAME, ... ) \
template<class T, class=void> struct TRAIT_NAME : std::false_type {}; \
template<class T> struct TRAIT_NAME< T, type_sink_t< decltype( __VA_ARGS__ ) > >: std::true_type {};

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 tipo T. Portanto:

MAKE_CODE_TRAIT( has_to_string, std::declval<T>().toString() )

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:

template<class T>
std::string optionalToString(T* obj) {
  return compiled_if< has_to_string >(*obj, [&](auto&& obj) {
    return obj.toString();
  }) *compiled_else ([&]{ 
    return "toString not defined";
  });
}

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.


Isso lida com casos particulares?
tower120

@ tower120 Eu teria que experimentar: como os modelos interagem com privado / público / protegido é um pouco obscuro para mim. Não importa onde você invoca, has_to_stringno entanto.
Yakk - Adam Nevraumont

mas você sabe, se olhar do outro lado ... Podemos alcançar membros protegidos da classe Derived. Talvez se colocar toda esta classe coisas dentro, e convertido a partir de estruturas para funções constexpr ...
tower120

Aqui, olhar para este coliru.stacked-crooked.com/a/ee94d16e7c07e093 eu simplesmente não posso fazê-lo constexpr
tower120

@ tower120 C ++ 1a faz com que funcione: coliru.stacked-crooked.com/a/d8cdfff24a171394
Yakk - Adam Nevraumont

10

Aqui estão alguns trechos de uso: * As entranhas de tudo isso estão mais abaixo

Procure um membro xem uma determinada classe. Pode ser var, func, classe, união ou enum:

CREATE_MEMBER_CHECK(x);
bool has_x = has_member_x<class_to_check_for_x>::value;

Verifique a função de membro void x():

//Func signature MUST have T as template variable here... simpler this way :\
CREATE_MEMBER_FUNC_SIG_CHECK(x, void (T::*)(), void__x);
bool has_func_sig_void__x = has_member_func_void__x<class_to_check_for_x>::value;

Verifique a variável de membro x:

CREATE_MEMBER_VAR_CHECK(x);
bool has_var_x = has_member_var_x<class_to_check_for_x>::value;

Verifique a classe de membro x:

CREATE_MEMBER_CLASS_CHECK(x);
bool has_class_x = has_member_class_x<class_to_check_for_x>::value;

Verifique a união dos membros x:

CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x<class_to_check_for_x>::value;

Verifique a enumeração de membro x:

CREATE_MEMBER_ENUM_CHECK(x);
bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;

Verifique qualquer função de membro, xindependentemente da assinatura:

CREATE_MEMBER_CHECK(x);
CREATE_MEMBER_VAR_CHECK(x);
CREATE_MEMBER_CLASS_CHECK(x);
CREATE_MEMBER_UNION_CHECK(x);
CREATE_MEMBER_ENUM_CHECK(x);
CREATE_MEMBER_FUNC_CHECK(x);
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;

OU

CREATE_MEMBER_CHECKS(x);  //Just stamps out the same macro calls as above.
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;

Detalhes e núcleo:

/*
    - Multiple inheritance forces ambiguity of member names.
    - SFINAE is used to make aliases to member names.
    - Expression SFINAE is used in just one generic has_member that can accept
      any alias we pass it.
*/

//Variadic to force ambiguity of class members.  C++11 and up.
template <typename... Args> struct ambiguate : public Args... {};

//Non-variadic version of the line above.
//template <typename A, typename B> struct ambiguate : public A, public B {};

template<typename A, typename = void>
struct got_type : std::false_type {};

template<typename A>
struct got_type<A> : std::true_type {
    typedef A type;
};

template<typename T, T>
struct sig_check : std::true_type {};

template<typename Alias, typename AmbiguitySeed>
struct has_member {
    template<typename C> static char ((&f(decltype(&C::value))))[1];
    template<typename C> static char ((&f(...)))[2];

    //Make sure the member name is consistently spelled the same.
    static_assert(
        (sizeof(f<AmbiguitySeed>(0)) == 1)
        , "Member name specified in AmbiguitySeed is different from member name specified in Alias, or wrong Alias/AmbiguitySeed has been specified."
    );

    static bool const value = sizeof(f<Alias>(0)) == 2;
};

Macros (El Diablo!):

CREATE_MEMBER_CHECK:

//Check for any member with given name, whether var, func, class, union, enum.
#define CREATE_MEMBER_CHECK(member)                                         \
                                                                            \
template<typename T, typename = std::true_type>                             \
struct Alias_##member;                                                      \
                                                                            \
template<typename T>                                                        \
struct Alias_##member <                                                     \
    T, std::integral_constant<bool, got_type<decltype(&T::member)>::value>  \
> { static const decltype(&T::member) value; };                             \
                                                                            \
struct AmbiguitySeed_##member { char member; };                             \
                                                                            \
template<typename T>                                                        \
struct has_member_##member {                                                \
    static const bool value                                                 \
        = has_member<                                                       \
            Alias_##member<ambiguate<T, AmbiguitySeed_##member>>            \
            , Alias_##member<AmbiguitySeed_##member>                        \
        >::value                                                            \
    ;                                                                       \
}

CREATE_MEMBER_VAR_CHECK:

//Check for member variable with given name.
#define CREATE_MEMBER_VAR_CHECK(var_name)                                   \
                                                                            \
template<typename T, typename = std::true_type>                             \
struct has_member_var_##var_name : std::false_type {};                      \
                                                                            \
template<typename T>                                                        \
struct has_member_var_##var_name<                                           \
    T                                                                       \
    , std::integral_constant<                                               \
        bool                                                                \
        , !std::is_member_function_pointer<decltype(&T::var_name)>::value   \
    >                                                                       \
> : std::true_type {}

CREATE_MEMBER_FUNC_SIG_CHECK:

//Check for member function with given name AND signature.
#define CREATE_MEMBER_FUNC_SIG_CHECK(func_name, func_sig, templ_postfix)    \
                                                                            \
template<typename T, typename = std::true_type>                             \
struct has_member_func_##templ_postfix : std::false_type {};                \
                                                                            \
template<typename T>                                                        \
struct has_member_func_##templ_postfix<                                     \
    T, std::integral_constant<                                              \
        bool                                                                \
        , sig_check<func_sig, &T::func_name>::value                         \
    >                                                                       \
> : std::true_type {}

CREATE_MEMBER_CLASS_CHECK:

//Check for member class with given name.
#define CREATE_MEMBER_CLASS_CHECK(class_name)               \
                                                            \
template<typename T, typename = std::true_type>             \
struct has_member_class_##class_name : std::false_type {};  \
                                                            \
template<typename T>                                        \
struct has_member_class_##class_name<                       \
    T                                                       \
    , std::integral_constant<                               \
        bool                                                \
        , std::is_class<                                    \
            typename got_type<typename T::class_name>::type \
        >::value                                            \
    >                                                       \
> : std::true_type {}

CREATE_MEMBER_UNION_CHECK:

//Check for member union with given name.
#define CREATE_MEMBER_UNION_CHECK(union_name)               \
                                                            \
template<typename T, typename = std::true_type>             \
struct has_member_union_##union_name : std::false_type {};  \
                                                            \
template<typename T>                                        \
struct has_member_union_##union_name<                       \
    T                                                       \
    , std::integral_constant<                               \
        bool                                                \
        , std::is_union<                                    \
            typename got_type<typename T::union_name>::type \
        >::value                                            \
    >                                                       \
> : std::true_type {}

CREATE_MEMBER_ENUM_CHECK:

//Check for member enum with given name.
#define CREATE_MEMBER_ENUM_CHECK(enum_name)                 \
                                                            \
template<typename T, typename = std::true_type>             \
struct has_member_enum_##enum_name : std::false_type {};    \
                                                            \
template<typename T>                                        \
struct has_member_enum_##enum_name<                         \
    T                                                       \
    , std::integral_constant<                               \
        bool                                                \
        , std::is_enum<                                     \
            typename got_type<typename T::enum_name>::type  \
        >::value                                            \
    >                                                       \
> : std::true_type {}

CREATE_MEMBER_FUNC_CHECK:

//Check for function with given name, any signature.
#define CREATE_MEMBER_FUNC_CHECK(func)          \
template<typename T>                            \
struct has_member_func_##func {                 \
    static const bool value                     \
        = has_member_##func<T>::value           \
        && !has_member_var_##func<T>::value     \
        && !has_member_class_##func<T>::value   \
        && !has_member_union_##func<T>::value   \
        && !has_member_enum_##func<T>::value    \
    ;                                           \
}

CREATE_MEMBER_CHECKS:

//Create all the checks for one member.  Does NOT include func sig checks.
#define CREATE_MEMBER_CHECKS(member)    \
CREATE_MEMBER_CHECK(member);            \
CREATE_MEMBER_VAR_CHECK(member);        \
CREATE_MEMBER_CLASS_CHECK(member);      \
CREATE_MEMBER_UNION_CHECK(member);      \
CREATE_MEMBER_ENUM_CHECK(member);       \
CREATE_MEMBER_FUNC_CHECK(member)

1
você tem alguma idéia de por que, se mudarmos 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?
v.oddou

Suponho que isso tenha algo a ver com o fato de que uma função livre não é uma classe ou estrutura. Essa técnica de deduzir a presença de um membro realmente se concentra no mecanismo de herança múltipla no C ++, forçando a ambiguidade entre uma classe de stub que existe apenas com o objetivo de hospedar o membro que você está procurando ou a classe que você está realmente procurando pelo membro pol. Essa é uma pergunta interessante, porém, não havia pensado nisso. Você pode procurar outras técnicas de verificação de membros do C ++ 11/14. Vi algumas coisas inteligentes no novo padrão.
Brett Rossier

Obrigado pela sua resposta, acho que talvez eu deva verificar mais profundamente as informações que você fornece sobre herança, porque até agora não vi nenhuma correlação entre simplesmente confiar na SFINAE para criar uma expressão que não seria correta, expressando o acesso a um membro em um parâmetro do tipo de modelo e herança múltipla. Mas acredito completamente que, em C ++, mesmo conceitos distantes podem sangrar um no outro. Agora, para funções livres, esta pergunta é interessante: stackoverflow.com/questions/26744589 A resposta do TC parece usar um truque para declarar um manequim para evitar o "identificador não declarado"
v.oddou

8

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

template<class T> struct has_const_begin
{
    typedef char (&Yes)[1];
    typedef char (&No)[2];

    template<class U> 
    static Yes test(U const * data, 
                    typename std::enable_if<std::is_same<
                             typename U::const_iterator, 
                             decltype(data->begin())
                    >::value>::type * = 0);
    static No test(...);
    static const bool value = sizeof(Yes) == sizeof(has_const_begin::test((typename std::remove_reference<T>::type*)0));
};

Observe que ele verifica a consistência do método e também funciona com tipos primitivos. (Quero dizerhas_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)

template<class T> struct has_foo
{
    typedef char (&Yes)[1];
    typedef char (&No)[2];

    template<class U>
    static Yes test(U * data, MyClass* arg1 = 0,
                    typename std::enable_if<std::is_void<
                             decltype(data->foo(*arg1, 1u))
                    >::value>::type * = 0);
    static No test(...);
    static const bool value = sizeof(Yes) == sizeof(has_foo::test((typename std::remove_reference<T>::type*)0));
};

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.


7

Agora isso foi um bom quebra-cabeça - ótima pergunta!

Aqui está uma alternativa à solução de Nicola Bonelli que não depende do typeofoperador 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:

14.6.2 Nomes dependentes [temp.dep]

§ 3

Na definição de um modelo de classe ou de um membro de um modelo de classe, se uma classe base do modelo de classe depende de um parâmetro de modelo, o escopo da classe base não é examinado durante a pesquisa não qualificada de nome no ponto de definição da classe modelo ou membro ou durante uma instanciação do modelo ou membro da classe.

Portanto, parece que quando o MSVC ou o Comeau consideram a toString()função de membro de Texecutar 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 ...


#include <iostream>
#include <string>

struct Hello
{
    std::string toString() {
        return "Hello";
    }
};

struct Generic {};


// the following namespace keeps the toString() method out of
//  most everything - except the other stuff in this
//  compilation unit

namespace {
    std::string toString()
    {
        return "toString not defined";
    }

    template <typename T>
    class optionalToStringImpl : public T
    {
    public:
        std::string doToString() {

            // in theory, the name lookup for this call to 
            //  toString() should find the toString() in 
            //  the base class T if one exists, but if one 
            //  doesn't exist in the base class, it'll 
            //  find the free toString() function in 
            //  the private namespace.
            //
            // This theory works for MSVC (all versions
            //  from VC6 to VC9) and Comeau C++, but
            //  does not work with MinGW 3.4.5 or 
            //  Digital Mars 8.42n
            //
            // I'm honestly not sure what the standard says 
            //  is the correct behavior here - it's sort 
            //  of like ADL (Argument Dependent Lookup - 
            //  also known as Koenig Lookup) but without
            //  arguments (except the implied "this" pointer)

            return toString();
        }
    };
}

template <typename T>
std::string optionalToString(T & obj)
{
    // ugly, hacky cast...
    optionalToStringImpl<T>* temp = reinterpret_cast<optionalToStringImpl<T>*>( &obj);

    return temp->doToString();
}



int
main(int argc, char *argv[])
{
    Hello helloObj;
    Generic genericObj;

    std::cout << optionalToString( helloObj) << std::endl;
    std::cout << optionalToString( genericObj) << std::endl;
    return 0;
}

1
Não, não é compatível com os padrões, embora eu ache que funcionará no GCC se você ativar a opção -fpermissive.
Leon Timmermans

Sei que os comentários não dão muito espaço, mas você poderia apontar informações sobre por que não é compatível com os padrões? (Não estou discutindo - Estou curioso)
Michael Burr

Mike B: o padrão diz na 3.10 p15: "Se um programa tentar acessar o valor armazenado de um objeto por meio de um valor l diferente de um dos seguintes tipos, o comportamento será indefinido" e essa lista não incluirá o caso de você Faz.
Johannes Schaub - litb 7/11/08

4
Não sei por que ele não adiciona outro comentário meu: sua chamada para toString não é qualificada. portanto, sempre chamará a função livre e nunca a da base, pois a classe base depende de um parâmetro do tipo de modelo.
Johannes Schaub - litb 7/11/08

@ Litb: Obrigado pelos ponteiros. Eu não acho que a 3.10 se aplica aqui. A chamada para toString () dentro de doToString () não está "acessando o valor armazenado de um objeto por meio de um lvalue". Mas seu segundo comentário está correto. Vou atualizar a resposta.
Michael Burr

6

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.


Hmm, tendo desenvolvido minha própria versão usando essas idéias, descobri que a idéia tem algumas desvantagens, então removi o código da minha resposta novamente. Uma é que todas as funções precisam ser públicas no tipo de destino. Portanto, você não pode procurar uma função "f" nisso: struct g { void f(); private: void f(int); };porque uma das funções é privada (isso ocorre porque o código faz using g::f;, o que faz com que falhe se alguma fnão estiver acessível).
Johannes Schaub - litb

6

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


6

Um exemplo usando SFINAE e especialização parcial de modelo, escrevendo uma Has_fooverificação de conceito:

#include <type_traits>
struct A{};

struct B{ int foo(int a, int b);};

struct C{void foo(int a, int b);};

struct D{int foo();};

struct E: public B{};

// available in C++17 onwards as part of <type_traits>
template<typename...>
using void_t = void;

template<typename T, typename = void> struct Has_foo: std::false_type{};

template<typename T> 
struct Has_foo<T, void_t<
    std::enable_if_t<
        std::is_same<
            int, 
            decltype(std::declval<T>().foo((int)0, (int)0))
        >::value
    >
>>: std::true_type{};


static_assert(not Has_foo<A>::value, "A does not have a foo");
static_assert(Has_foo<B>::value, "B has a foo");
static_assert(not Has_foo<C>::value, "C has a foo with the wrong return. ");
static_assert(not Has_foo<D>::value, "D has a foo with the wrong arguments. ");
static_assert(Has_foo<E>::value, "E has a foo since it inherits from B");

5

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.

/** 
 * @class      : HAS_TYPEDEF
 * @brief      : This macro will be used to check if a class has a particular
 * typedef or not.
 * @param typedef_name : Name of Typedef
 * @param name  : Name of struct which is going to be run the test for
 * the given particular typedef specified in typedef_name
 */
#define HAS_TYPEDEF(typedef_name, name)                           \
   template <typename T>                                          \
   struct name {                                                  \
      typedef char yes[1];                                        \
      typedef char no[2];                                         \
      template <typename U>                                       \
      struct type_check;                                          \
      template <typename _1>                                      \
      static yes& chk(type_check<typename _1::typedef_name>*);    \
      template <typename>                                         \
      static no& chk(...);                                        \
      static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
   }

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.

/** 
 * @class      : HAS_MEM_FUNC
 * @brief      : This macro will be used to check if a class has a particular
 * member function implemented in the public section or not. 
 * @param func : Name of Member Function
 * @param name : Name of struct which is going to be run the test for
 * the given particular member function name specified in func
 * @param return_type: Return type of the member function
 * @param ellipsis(...) : Since this is macro should provide test case for every
 * possible member function we use variadic macros to cover all possibilities
 */
#define HAS_MEM_FUNC(func, name, return_type, ...)                \
   template <typename T>                                          \
   struct name {                                                  \
      typedef return_type (T::*Sign)(__VA_ARGS__);                \
      typedef char yes[1];                                        \
      typedef char no[2];                                         \
      template <typename U, U>                                    \
      struct type_check;                                          \
      template <typename _1>                                      \
      static yes& chk(type_check<Sign, &_1::func>*);              \
      template <typename>                                         \
      static no& chk(...);                                        \
      static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
   }

Podemos usar as 2 macros acima para executar as verificações de has_typedef e has_mem_func como:

class A {
public:
  typedef int check;
  void check_function() {}
};

class B {
public:
  void hello(int a, double b) {}
  void hello() {}
};

HAS_MEM_FUNC(check_function, has_check_function, void, void);
HAS_MEM_FUNC(hello, hello_check, void, int, double);
HAS_MEM_FUNC(hello, hello_void_check, void, void);
HAS_TYPEDEF(check, has_typedef_check);

int main() {
  std::cout << "Check Function A:" << has_check_function<A>::value << std::endl;
  std::cout << "Check Function B:" << has_check_function<B>::value << std::endl;
  std::cout << "Hello Function A:" << hello_check<A>::value << std::endl;
  std::cout << "Hello Function B:" << hello_check<B>::value << std::endl;
  std::cout << "Hello void Function A:" << hello_void_check<A>::value << std::endl;
  std::cout << "Hello void Function B:" << hello_void_check<B>::value << std::endl;
  std::cout << "Check Typedef A:" << has_typedef_check<A>::value << std::endl;
  std::cout << "Check Typedef B:" << has_typedef_check<B>::value << std::endl;
}

Você pode melhorar isso para suportar funções-membro com argumentos de modelo. Altere o modelo <nome do tipo T> para o modelo <nome do tipo T, nome do tipo ... Args> e, em seguida, você pode usar "Args ..." nas elipses da macro para criar uma estrutura de verificação com argumentos de modelo variados. por exemplo. Detectar o método "void onNext (const T &)" 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" ); };
ACyclic 1/16/16

4

Ninguém estranho sugeriu o seguinte truque legal que vi uma vez neste site:

template <class T>
struct has_foo
{
    struct S { void foo(...); };
    struct derived : S, T {};

    template <typename V, V> struct W {};

    template <typename X>
    char (&test(W<void (X::*)(), &X::foo> *))[1];

    template <typename>
    char (&test(...))[2];

    static const bool value = sizeof(test<derived>(0)) == 1;
};

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.


3

O modelo genérico que pode ser usado para verificar se algum "recurso" é suportado pelo tipo:

#include <type_traits>

template <template <typename> class TypeChecker, typename Type>
struct is_supported
{
    // these structs are used to recognize which version
    // of the two functions was chosen during overload resolution
    struct supported {};
    struct not_supported {};

    // this overload of chk will be ignored by SFINAE principle
    // if TypeChecker<Type_> is invalid type
    template <typename Type_>
    static supported chk(typename std::decay<TypeChecker<Type_>>::type *);

    // ellipsis has the lowest conversion rank, so this overload will be
    // chosen during overload resolution only if the template overload above is ignored
    template <typename Type_>
    static not_supported chk(...);

    // if the template overload of chk is chosen during
    // overload resolution then the feature is supported
    // if the ellipses overload is chosen the the feature is not supported
    static constexpr bool value = std::is_same<decltype(chk<Type>(nullptr)),supported>::value;
};

O modelo que verifica se existe um método foocompatível com a assinaturadouble(const char*)

// if T doesn't have foo method with the signature that allows to compile the bellow
// expression then instantiating this template is Substitution Failure (SF)
// which Is Not An Error (INAE) if this happens during overload resolution
template <typename T>
using has_foo = decltype(double(std::declval<T>().foo(std::declval<const char*>())));

Exemplos

// types that support has_foo
struct struct1 { double foo(const char*); };            // exact signature match
struct struct2 { int    foo(const std::string &str); }; // compatible signature
struct struct3 { float  foo(...); };                    // compatible ellipsis signature
struct struct4 { template <typename T>
                 int    foo(T t); };                    // compatible template signature

// types that do not support has_foo
struct struct5 { void        foo(const char*); }; // returns void
struct struct6 { std::string foo(const char*); }; // std::string can't be converted to double
struct struct7 { double      foo(      int *); }; // const char* can't be converted to int*
struct struct8 { double      bar(const char*); }; // there is no foo method

int main()
{
    std::cout << std::boolalpha;

    std::cout << is_supported<has_foo, int    >::value << std::endl; // false
    std::cout << is_supported<has_foo, double >::value << std::endl; // false

    std::cout << is_supported<has_foo, struct1>::value << std::endl; // true
    std::cout << is_supported<has_foo, struct2>::value << std::endl; // true
    std::cout << is_supported<has_foo, struct3>::value << std::endl; // true
    std::cout << is_supported<has_foo, struct4>::value << std::endl; // true

    std::cout << is_supported<has_foo, struct5>::value << std::endl; // false
    std::cout << is_supported<has_foo, struct6>::value << std::endl; // false
    std::cout << is_supported<has_foo, struct7>::value << std::endl; // false
    std::cout << is_supported<has_foo, struct8>::value << std::endl; // false

    return 0;
}

http://coliru.stacked-crooked.com/a/83c6a631ed42cea4


Existe uma maneira de incorporar o has_foona chamada de modelo de is_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 um has_foopara cada assinatura de função diferente que desejo verificar antes de poder verificar a função?
CJCombrink

2

Que tal esta solução?

#include <type_traits>

template <typename U, typename = void> struct hasToString : std::false_type { };

template <typename U>
struct hasToString<U,
  typename std::enable_if<bool(sizeof(&U::toString))>::type
> : std::true_type { };

Falha se toStringestiver sobrecarregado, como &U::toStringé ambíguo.
Yakk - Adam Nevraumont

@Yakk Eu acho que um elenco pode resolver esse problema.
precisa saber é o seguinte

2

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 ():

template<typename T> T declval(void);

struct fake_void { };
template<typename T> T &operator,(T &,fake_void);
template<typename T> T const &operator,(T const &,fake_void);
template<typename T> T volatile &operator,(T volatile &,fake_void);
template<typename T> T const volatile &operator,(T const volatile &,fake_void);

struct yes { char v[1]; };
struct no  { char v[2]; };
template<bool> struct yes_no:yes{};
template<> struct yes_no<false>:no{};

template<typename T>
struct has_awesome_member {
 template<typename U> static yes_no<(sizeof((
   declval<U>().awesome_member(),fake_void()
  ))!=0)> check(int);
 template<typename> static no check(...);
 enum{value=sizeof(check<T>(0)) == sizeof(yes)};
};


struct foo { int awesome_member(void); };
struct bar { };
struct foo_void { void awesome_member(void); };
struct wrong_params { void awesome_member(int); };

static_assert(has_awesome_member<foo>::value,"");
static_assert(!has_awesome_member<bar>::value,"");
static_assert(has_awesome_member<foo_void>::value,"");
static_assert(!has_awesome_member<wrong_params>::value,"");

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'.


Não, mas eu mesmo estou declarando e não usa rvalue (veja a parte superior do meu código). Ou você pode simplesmente se convencer e experimentar a demonstração ao vivo no modo c ++ 98. PS: static_assert não é c ++ 98 também, mas existem soluções alternativas (demonstração ao vivo)
user3296587

d'oh! perdeu isso. :-)
Ian Ni-Lewis

Suas declarações estáticas não funcionam. Você precisa usar o tamanho da matriz -1 em vez de 0 (tente colocar 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.
the

Estou assumindo que você está usando g ++. Observe que o gcc / g ++ possui uma extensão que permite uma matriz de tamanho zero ( gcc.gnu.org/onlinedocs/gcc/Zero-Length.html )
user3296587

Você poderia reescrever isso para não sobrecarregar o operador? por exemplo, escolha outro operador? Além disso, evite a poluição do espaço para nome com algo diferente de has_awesome_member?
einpoklum

1

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:

#include <string>
#include <vector>

HAS_MEM(bar)
HAS_MEM_FUN_CALL(bar)

struct test
{
   void bar(int);
   void bar(double);
   void bar(int,double);

   template < typename T >
   typename std::enable_if< not std::is_integral<T>::value >::type
   bar(const T&, int=0){}

   template < typename T >
   typename std::enable_if< std::is_integral<T>::value >::type
   bar(const std::vector<T>&, T*){}

   template < typename T >
   int bar(const std::string&, int){}
};

Agora você pode usá-lo assim:

int main(int argc, const char * argv[])
{
   static_assert( has_mem_bar<test>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(char const*,long)>::value , "");
   static_assert( has_valid_mem_fun_call_bar<test(std::string&,long)>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(std::vector<int>, int*)>::value , "");
   static_assert( has_no_viable_mem_fun_call_bar<test(std::vector<double>, double*)>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(int)>::value , "");
   static_assert( std::is_same<void,result_of_mem_fun_call_bar<test(int)>::type>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(int,double)>::value , "");
   static_assert( not has_valid_mem_fun_call_bar<test(int,double,int)>::value , "");

   static_assert( not has_ambiguous_mem_fun_call_bar<test(double)>::value , "");
   static_assert( has_ambiguous_mem_fun_call_bar<test(unsigned)>::value , "");

   static_assert( has_viable_mem_fun_call_bar<test(unsigned)>::value , "");
   static_assert( has_viable_mem_fun_call_bar<test(int)>::value , "");

   static_assert( has_no_viable_mem_fun_call_bar<test(void)>::value , "");

   return 0;
}

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.

#pragma once

#if __cplusplus >= 201103

#include <utility>
#include <type_traits>

#define HAS_MEM(mem)                                                                                     \
                                                                                                     \
template < typename T >                                                                               \
struct has_mem_##mem                                                                                  \
{                                                                                                     \
  struct yes {};                                                                                     \
  struct no  {};                                                                                     \
                                                                                                     \
  struct ambiguate_seed { char mem; };                                                               \
  template < typename U > struct ambiguate : U, ambiguate_seed {};                                   \
                                                                                                     \
  template < typename U, typename = decltype(&U::mem) > static constexpr no  test(int);              \
  template < typename                                 > static constexpr yes test(...);              \
                                                                                                     \
  static bool constexpr value = std::is_same<decltype(test< ambiguate<T> >(0)),yes>::value ;         \
  typedef std::integral_constant<bool,value>    type;                                                \
};


#define HAS_MEM_FUN_CALL(memfun)                                                                         \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_valid_mem_fun_call_##memfun;                                                               \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_valid_mem_fun_call_##memfun< T(Args...) >                                                  \
{                                                                                                     \
  struct yes {};                                                                                     \
  struct no  {};                                                                                     \
                                                                                                     \
  template < typename U, bool = has_mem_##memfun<U>::value >                                         \
  struct impl                                                                                        \
  {                                                                                                  \
     template < typename V, typename = decltype(std::declval<V>().memfun(std::declval<Args>()...)) > \
     struct test_result { using type = yes; };                                                       \
                                                                                                     \
     template < typename V > static constexpr typename test_result<V>::type test(int);               \
     template < typename   > static constexpr                            no test(...);               \
                                                                                                     \
     static constexpr bool value = std::is_same<decltype(test<U>(0)),yes>::value;                    \
     using type = std::integral_constant<bool, value>;                                               \
  };                                                                                                 \
                                                                                                     \
  template < typename U >                                                                            \
  struct impl<U,false> : std::false_type {};                                                         \
                                                                                                     \
  static constexpr bool value = impl<T>::value;                                                      \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_ambiguous_mem_fun_call_##memfun;                                                           \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_ambiguous_mem_fun_call_##memfun< T(Args...) >                                              \
{                                                                                                     \
  struct ambiguate_seed { void memfun(...); };                                                       \
                                                                                                     \
  template < class U, bool = has_mem_##memfun<U>::value >                                            \
  struct ambiguate : U, ambiguate_seed                                                               \
  {                                                                                                  \
    using ambiguate_seed::memfun;                                                                    \
    using U::memfun;                                                                                 \
  };                                                                                                 \
                                                                                                     \
  template < class U >                                                                               \
  struct ambiguate<U,false> : ambiguate_seed {};                                                     \
                                                                                                     \
  static constexpr bool value = not has_valid_mem_fun_call_##memfun< ambiguate<T>(Args...) >::value; \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_viable_mem_fun_call_##memfun;                                                              \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_viable_mem_fun_call_##memfun< T(Args...) >                                                 \
{                                                                                                     \
  static constexpr bool value = has_valid_mem_fun_call_##memfun<T(Args...)>::value                   \
                             or has_ambiguous_mem_fun_call_##memfun<T(Args...)>::value;              \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_no_viable_mem_fun_call_##memfun;                                                           \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_no_viable_mem_fun_call_##memfun < T(Args...) >                                             \
{                                                                                                     \
  static constexpr bool value = not has_viable_mem_fun_call_##memfun<T(Args...)>::value;             \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct result_of_mem_fun_call_##memfun;                                                               \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct result_of_mem_fun_call_##memfun< T(Args...) >                                                  \
{                                                                                                     \
  using type = decltype(std::declval<T>().memfun(std::declval<Args>()...));                          \
};

#endif


1

Você pode pular toda a metaprogramação em C ++ 14 e apenas escrever isso usando fit::conditionala biblioteca Fit :

template<class T>
std::string optionalToString(T* x)
{
    return fit::conditional(
        [](auto* obj) -> decltype(obj->toString()) { return obj->toString(); },
        [](auto*) { return "toString not defined"; }
    )(x);
}

Você também pode criar a função diretamente das lambdas:

FIT_STATIC_LAMBDA_FUNCTION(optionalToString) = fit::conditional(
    [](auto* obj) -> decltype(obj->toString(), std::string()) { return obj->toString(); },
    [](auto*) -> std::string { return "toString not defined"; }
);

No entanto, se você estiver usando um compilador que não suporta lambdas genéricos, precisará escrever objetos de função separados:

struct withToString
{
    template<class T>
    auto operator()(T* obj) const -> decltype(obj->toString(), std::string())
    {
        return obj->toString();
    }
};

struct withoutToString
{
    template<class T>
    std::string operator()(T*) const
    {
        return "toString not defined";
    }
};

FIT_STATIC_FUNCTION(optionalToString) = fit::conditional(
    withToString(),
    withoutToString()
);

1
Quão fácil é escrever isso para não precisar depender de fitnenhuma biblioteca que não seja a padrão?
einpoklum

1

Com o C ++ 20, você pode escrever o seguinte:

template<typename T>
concept has_toString = requires(const T& t) {
    t.toString();
};

template<typename T>
std::string optionalToString(const T& obj)
{
    if constexpr (has_toString<T>)
        return obj.toString();
    else
        return "toString not defined";
}

0

Aqui está um exemplo do código de trabalho.

template<typename T>
using toStringFn = decltype(std::declval<const T>().toString());

template <class T, toStringFn<T>* = nullptr>
std::string optionalToString(const T* obj, int)
{
    return obj->toString();
}

template <class T>
std::string optionalToString(const T* obj, long)
{
    return "toString not defined";
}

int main()
{
    A* a;
    B* b;

    std::cout << optionalToString(a, 0) << std::endl; // This is A
    std::cout << optionalToString(b, 0) << std::endl; // toString not defined
}

toStringFn<T>* = nullptrhabilitará a função que recebe um intargumento extra que tem uma prioridade sobre a função que recebe longquando chamada com 0.

Você pode usar o mesmo princípio para as funções que retornam truese a função for implementada.

template <typename T>
constexpr bool toStringExists(long)
{
    return false;
}

template <typename T, toStringFn<T>* = nullptr>
constexpr bool toStringExists(int)
{
    return true;
}


int main()
{
    A* a;
    B* b;

    std::cout << toStringExists<A>(0) << std::endl; // true
    std::cout << toStringExists<B>(0) << std::endl; // false
}

0

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:

#include <iostream>
#include <string>

struct Generic {};    
struct HasMember 
{
  HasMember() : _a(1) {};
  int _a;
};    

// SFINAE test
template <typename T>
class S : public T
{
public:
  std::string foo (std::string b)
  {
    return foo2<T>(b,0);
  }

protected:
  template <typename T> std::string foo2 (std::string b, decltype (T::_a))
  {
    return b + std::to_string(T::_a);
  }
  template <typename T> std::string foo2 (std::string b, ...)
  {
    return b + "No";
  }
};

int main(int argc, char *argv[])
{
  S<HasMember> d1;
  S<Generic> d2;

  std::cout << d1.foo("HasMember: ") << std::endl;
  std::cout << d2.foo("Generic: ") << std::endl;
  return 0;
}

0

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.

#include <type_traits>

template<typename T, typename F>
constexpr auto is_valid(F&& f) -> decltype(f(std::declval<T>()), true) { return true; }

template<typename>
constexpr bool is_valid(...) { return false; }

#define IS_VALID(T, EXPR) is_valid<T>( [](auto&& obj)->decltype(obj.EXPR){} )

Exemplo

#include <iostream>

struct Example {
    int Foo;
    void Bar() {}
    std::string toString() { return "Hello from toString()!"; }
};

struct Example2 {
    int X;
};

template<class T>
std::string optionalToString(T* obj)
{
    if constexpr(IS_VALID(T, toString()))
        return obj->toString();
    else
        return "toString not defined";
}

int main() {
    static_assert(IS_VALID(Example, Foo));
    static_assert(IS_VALID(Example, Bar()));
    static_assert(!IS_VALID(Example, ZFoo));
    static_assert(!IS_VALID(Example, ZBar()));

    Example e1;
    Example2 e2;

    std::cout << "e1: " << optionalToString(&e1) << "\n";
    std::cout << "e1: " << optionalToString(&e2) << "\n";
}

-1
template<class T>
auto optionalToString(T* obj)
->decltype( obj->toString(), std::string() )
{
     return obj->toString();
}

template<class T>
auto optionalToString(T* obj)
->decltype( std::string() )
{
     throw "Error!";
}

6
"Não precisamos de descrições de resposta" ... adicione uma descrição informativa à sua resposta para melhorá-la. Obrigado.
YesThatIsMyName
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.