Verifique se uma classe tem uma função de membro de uma determinada assinatura

135

Estou pedindo um truque de modelo para detectar se uma classe tem uma função de membro específica de uma determinada assinatura.

O problema é semelhante ao citado aqui http://www.gotw.ca/gotw/071.htm, mas não é o mesmo: no item do livro de Sutter, ele respondeu à pergunta que uma classe C DEVE FORNECER a uma função membro com uma assinatura específica, caso contrário, o programa não será compilado. No meu problema, preciso fazer algo se uma classe tiver essa função, caso contrário, faça "algo mais".

Um problema semelhante foi enfrentado pelo boost :: serialization, mas não gosto da solução que eles adotaram: uma função de modelo que chama por padrão uma função livre (que você precisa definir) com uma assinatura específica, a menos que você defina uma função-membro específica ( no caso "serialize", que usa 2 parâmetros de um determinado tipo) com uma assinatura específica, caso contrário, ocorrerá um erro de compilação. Isso é para implementar serialização intrusiva e não intrusiva.

Não gosto dessa solução por dois motivos:

  1. Para não ser intrusivo, você deve substituir a função global "serialize" que está no namespace boost :: serialization, para que você tenha EM SEU CÓDIGO DO CLIENTE para abrir o aumento do namespace e a serialização do namespace!
  2. A pilha para resolver essa bagunça era de 10 a 12 invocações de funções.

Preciso definir um comportamento personalizado para classes que não possuem essa função de membro e minhas entidades estão dentro de espaços para nome diferentes (e não quero substituir uma função global definida em um espaço para nome enquanto estou em outro)

Você pode me dar uma dica para resolver esse quebra-cabeça?

ugasoft
fonte
1
Pergunta semelhante: stackoverflow.com/questions/257288
Johannes Schaub - litb
@ R.MartinhoFernandes Que tipo de resposta você está procurando? Esta resposta de Mike Kinghan é bastante aprofundada e está usando material C ++ 11.
JROK
@ R.MartinhoFernandes Talvez esta seja a versão moderna que você está procurando?
Daniel Frey

Respostas:

90

Não tenho certeza se o entendi corretamente, mas você pode explorar o SFINAE para detectar a presença de funções em tempo de compilação. Exemplo do meu código (testa se a classe possui a função de membro size_t used_memory () const).

template<typename T>
struct HasUsedMemoryMethod
{
    template<typename U, size_t (U::*)() const> struct SFINAE {};
    template<typename U> static char Test(SFINAE<U, &U::used_memory>*);
    template<typename U> static int Test(...);
    static const bool Has = sizeof(Test<T>(0)) == sizeof(char);
};

template<typename TMap>
void ReportMemUsage(const TMap& m, std::true_type)
{
        // We may call used_memory() on m here.
}
template<typename TMap>
void ReportMemUsage(const TMap&, std::false_type)
{
}
template<typename TMap>
void ReportMemUsage(const TMap& m)
{
    ReportMemUsage(m, 
        std::integral_constant<bool, HasUsedMemoryMethod<TMap>::Has>());
}
yrp
fonte
14
wtf é isso ??? é código c ++ legal ?? você pode escrever "template <nome do tipo U, size_t (U :: *) () const>"? mas ... é uma ótima e nova solução! Agradeço, vou analisar melhor amanhã com meus colegas ... ótimo!
ugasoft 17/09/08
2
O exemplo está ausente na definição de 'int_to_type'. Obviamente, isso não contribui para a resposta, mas significa que as pessoas podem ver seu código em ação após um rápido recortar e colar.
Richard Corden
2
Uma definição simples de int_to_type pode ser: 'template <int N> struct int_to_type {};'. Muitas implementações mantêm o valor do parâmetro N em uma enum ou em uma constante inteira estática (template <int N> struct int_to_type {enum {value = N};}; / template <int N> struct int_to_type {static const int value = N;})
David Rodríguez - dribeas 03/09/09
2
Simplesmente tome boost :: integral_constant em vez de int_to_type.
Vadim Ferderer 28/09/09
2
@JohanLundberg É uma função de membro de ponteiro para (não estático). Por exemplo size_t(std::vector::*p)() = &std::vector::size;,.
Reinstale Monica
133

Aqui está uma possível implementação baseada nos recursos do C ++ 11. Ele detecta corretamente a função, mesmo que seja herdada (diferente da solução na resposta aceita, como Mike Kinghan observa em sua resposta ).

A função para a qual esse snippet testa é chamada serialize:

#include <type_traits>

// Primary template with a static assertion
// for a meaningful error message
// if it ever gets instantiated.
// We could leave it undefined if we didn't care.

template<typename, typename T>
struct has_serialize {
    static_assert(
        std::integral_constant<T, false>::value,
        "Second template parameter needs to be of function type.");
};

// specialization that does the checking

template<typename C, typename Ret, typename... Args>
struct has_serialize<C, Ret(Args...)> {
private:
    template<typename T>
    static constexpr auto check(T*)
    -> typename
        std::is_same<
            decltype( std::declval<T>().serialize( std::declval<Args>()... ) ),
            Ret    // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        >::type;  // attempt to call it and see if the return type is correct

    template<typename>
    static constexpr std::false_type check(...);

    typedef decltype(check<C>(0)) type;

public:
    static constexpr bool value = type::value;
};

Uso:

struct X {
     int serialize(const std::string&) { return 42; } 
};

struct Y : X {};

std::cout << has_serialize<Y, int(const std::string&)>::value; // will print 1
jrok
fonte
Isso funciona se Y não tiver um método chamado "serializar"? Não vejo como isso retornaria um valor falso se o método "serialize" não existisse.
Collin
1
@Collin nesse caso, a substituição do parâmetro template falha na primeira sobrecarga de verificação e é descartada do conjunto de sobrecargas. Ele volta ao segundo que retorna false_type. Este não é um erro do compilador porque o princípio SFINAE.
JROK
1
@ elios264 Não há. Você pode usar uma macro para escrever um modelo para cada função que deseja verificar.
JROK
1
Alguma razão específica para o argumento de verificação ser do tipo T * em vez de T ou T &?
Shibumi
1
Mas e se o serializepróprio aceitar um modelo. Existe uma maneira de testar a serializeexistência sem digitar o tipo exato?
Hi-Angel
37

A resposta aceita para esta questão da introspecção de função membro de compilação, embora seja bastante popular, tem um problema que pode ser observado no seguinte programa:

#include <type_traits>
#include <iostream>
#include <memory>

/*  Here we apply the accepted answer's technique to probe for the
    the existence of `E T::operator*() const`
*/
template<typename T, typename E>
struct has_const_reference_op
{
    template<typename U, E (U::*)() const> struct SFINAE {};
    template<typename U> static char Test(SFINAE<U, &U::operator*>*);
    template<typename U> static int Test(...);
    static const bool value = sizeof(Test<T>(0)) == sizeof(char);
};

using namespace std;

/* Here we test the `std::` smart pointer templates, including the
    deprecated `auto_ptr<T>`, to determine in each case whether
    T = (the template instantiated for `int`) provides 
    `int & T::operator*() const` - which all of them in fact do.
*/ 
int main(void)
{
    cout << has_const_reference_op<auto_ptr<int>,int &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int &>::value;
    cout << has_const_reference_op<shared_ptr<int>,int &>::value << endl;
    return 0;
}

Construído com GCC 4.6.3, os resultados do programa 110- informando-nos que T = std::shared_ptr<int>não não fornecer int & T::operator*() const.

Se você ainda não é sábio quanto a essa pegadinha, uma olhada na definição de std::shared_ptr<T>no cabeçalho <memory>lançará luz. Nessa implementação, std::shared_ptr<T>é derivado de uma classe base da qual ela herda operator*() const. Portanto, a instanciação de modelo SFINAE<U, &U::operator*>que constitui "encontrar" o operador U = std::shared_ptr<T>não ocorrerá, porque std::shared_ptr<T>não tem operator*()por si só e a instanciação de modelo não "faz herança".

Esse problema não afeta a conhecida abordagem SFINAE, usando "The sizeof () Trick", para detectar apenas se Ttem alguma função de membro mf(veja, por exemplo, esta resposta e comentários). Mas estabelecer que T::mfexiste muitas vezes (geralmente?) Não é bom o suficiente: você também pode precisar estabelecer que possui a assinatura desejada. É aí que a técnica ilustrada é pontuada. A variante apontada da assinatura desejada é inscrita em um parâmetro de um tipo de modelo que deve ser satisfeito &T::mfpara que o probe SFINAE seja bem-sucedido. Mas essa técnica de instanciação de modelo fornece a resposta errada quando T::mfé herdada.

Uma técnica SFINAE segura para introspecção de tempo de compilação T::mfdeve evitar o uso de &T::mfum argumento de modelo para instanciar um tipo do qual depende a resolução do modelo de função SFINAE. Em vez disso, a resolução da função de modelo SFINAE pode depender apenas de declarações de tipo exatamente pertinentes usadas como tipos de argumento da função de teste SFINAE sobrecarregada.

Como resposta à pergunta que cumpre essa restrição, ilustrarei a detecção de compilações de E T::operator*() const, arbitrárias Te E. O mesmo padrão se aplicará mutatis mutandis para investigar qualquer outra assinatura de método membro.

#include <type_traits>

/*! The template `has_const_reference_op<T,E>` exports a
    boolean constant `value that is true iff `T` provides
    `E T::operator*() const`
*/ 
template< typename T, typename E>
struct has_const_reference_op
{
    /* SFINAE operator-has-correct-sig :) */
    template<typename A>
    static std::true_type test(E (A::*)() const) {
        return std::true_type();
    }

    /* SFINAE operator-exists :) */
    template <typename A> 
    static decltype(test(&A::operator*)) 
    test(decltype(&A::operator*),void *) {
        /* Operator exists. What about sig? */
        typedef decltype(test(&A::operator*)) return_type; 
        return return_type();
    }

    /* SFINAE game over :( */
    template<typename A>
    static std::false_type test(...) {
        return std::false_type(); 
    }

    /* This will be either `std::true_type` or `std::false_type` */
    typedef decltype(test<T>(0,0)) type;

    static const bool value = type::value; /* Which is it? */
};

Nesta solução, a função de sonda SFINAE sobrecarregada test()é "invocada recursivamente". (É claro que na verdade não é invocado; apenas possui os tipos de retorno de invocações hipotéticas resolvidos pelo compilador.)

Precisamos investigar pelo menos um e no máximo dois pontos de informação:

  • Existe T::operator*()mesmo? Caso contrário, terminamos.
  • Dado que T::operator*()existe, é a sua assinatura E T::operator*() const?

Obtemos as respostas avaliando o tipo de retorno de uma única chamada para test(0,0). Isso é feito por:

    typedef decltype(test<T>(0,0)) type;

Essa chamada pode ser resolvida com a /* SFINAE operator-exists :) */sobrecarga de test()ou com a /* SFINAE game over :( */sobrecarga. Não pode resolver a /* SFINAE operator-has-correct-sig :) */sobrecarga, porque esse espera apenas um argumento e estamos passando dois.

Por que estamos passando dois? Simplesmente para forçar a resolução a excluir /* SFINAE operator-has-correct-sig :) */. O segundo argumento não tem outro significado.

Essa chamada para test(0,0)resolverá /* SFINAE operator-exists :) */apenas no caso de o primeiro argumento 0 satisfazer o primeiro tipo de parâmetro dessa sobrecarga, ou seja decltype(&A::operator*), com A = T. 0 satisfará esse tipo, caso T::operator*exista.

Vamos supor que o compilador diga Sim para isso. Então, ele continua /* SFINAE operator-exists :) */e precisa determinar o tipo de retorno da chamada de função, que nesse caso é decltype(test(&A::operator*))- o tipo de retorno de mais uma chamada para test().

Desta vez, estamos passando apenas um argumento, &A::operator*que sabemos que existe, ou não estaríamos aqui. Uma chamada para test(&A::operator*)pode resolver para /* SFINAE operator-has-correct-sig :) */ou novamente para /* SFINAE game over :( */. A chamada corresponderá /* SFINAE operator-has-correct-sig :) */caso &A::operator*satisfaça o tipo de parâmetro único dessa sobrecarga, ou seja E (A::*)() const, com A = T.

O compilador dirá Sim aqui se T::operator*tiver a assinatura desejada e, em seguida, novamente terá que avaliar o tipo de retorno da sobrecarga. Não há mais "recursões" agora: é std::true_type.

Se o compilador não escolhe /* SFINAE operator-exists :) */para a chamada test(0,0)ou não escolher /* SFINAE operator-has-correct-sig :) */ para a chamada test(&A::operator*), em seguida, em ambos os casos ele vai com /* SFINAE game over :( */e o tipo de retorno final é std::false_type.

Aqui está um programa de teste que mostra o modelo produzindo as respostas esperadas em uma amostra variada de casos (GCC 4.6.3 novamente).

// To test
struct empty{};

// To test 
struct int_ref
{
    int & operator*() const {
        return *_pint;
    }
    int & foo() const {
        return *_pint;
    }
    int * _pint;
};

// To test 
struct sub_int_ref : int_ref{};

// To test 
template<typename E>
struct ee_ref
{
    E & operator*() {
        return *_pe;
    }
    E & foo() const {
        return *_pe;
    }
    E * _pe;
};

// To test 
struct sub_ee_ref : ee_ref<char>{};

using namespace std;

#include <iostream>
#include <memory>
#include <vector>

int main(void)
{
    cout << "Expect Yes" << endl;
    cout << has_const_reference_op<auto_ptr<int>,int &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int &>::value;
    cout << has_const_reference_op<shared_ptr<int>,int &>::value;
    cout << has_const_reference_op<std::vector<int>::iterator,int &>::value;
    cout << has_const_reference_op<std::vector<int>::const_iterator,
            int const &>::value;
    cout << has_const_reference_op<int_ref,int &>::value;
    cout << has_const_reference_op<sub_int_ref,int &>::value  << endl;
    cout << "Expect No" << endl;
    cout << has_const_reference_op<int *,int &>::value;
    cout << has_const_reference_op<unique_ptr<int>,char &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int const &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int>::value;
    cout << has_const_reference_op<unique_ptr<long>,int &>::value;
    cout << has_const_reference_op<int,int>::value;
    cout << has_const_reference_op<std::vector<int>,int &>::value;
    cout << has_const_reference_op<ee_ref<int>,int &>::value;
    cout << has_const_reference_op<sub_ee_ref,int &>::value;
    cout << has_const_reference_op<empty,int &>::value  << endl;
    return 0;
}

Existem novas falhas nessa idéia? Pode ser tornado mais genérico sem, mais uma vez, cair no obstáculo que evita?

Mike Kinghan
fonte
16

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)
Brett Rossier
fonte
1
Isso é ótimo; seria bom colocar isso em uma única biblioteca de arquivos de cabeçalho.
Allan
12

Isso deve ser suficiente, se você souber o nome da função de membro que está esperando. (Nesse caso, a função bla falha ao instanciar se não houver função membro (escrever uma que funcione de qualquer maneira é difícil, porque há uma falta de especialização parcial da função. Você pode precisar usar modelos de classe) Além disso, a estrutura de ativação (que é semelhante a enable_if) também pode ser modelado no tipo de função que você deseja que ele tenha como membro.

template <typename T, int (T::*) ()> struct enable { typedef T type; };
template <typename T> typename enable<T, &T::i>::type bla (T&);
struct A { void i(); };
struct B { int i(); };
int main()
{
  A a;
  B b;
  bla(b);
  bla(a);
}
coppro
fonte
4
thaks! é semelhante à solução proposta pelo yrp. Eu não sabia que esse modelo pode ser modelado sobre funções de membro. Esse é um novo recurso que aprendi hoje! ... e uma nova lição: "nunca diga que você é especialista em c ++" :)
ugasoft
7

Aqui está uma abordagem mais simples da resposta de Mike Kinghan. Isso detectará métodos herdados. Ele também verificará a assinatura exata (diferente da abordagem do jrok, que permite conversões de argumentos).

template <class C>
class HasGreetMethod
{
    template <class T>
    static std::true_type testSignature(void (T::*)(const char*) const);

    template <class T>
    static decltype(testSignature(&T::greet)) test(std::nullptr_t);

    template <class T>
    static std::false_type test(...);

public:
    using type = decltype(test<C>(nullptr));
    static const bool value = type::value;
};

struct A { void greet(const char* name) const; };
struct Derived : A { };
static_assert(HasGreetMethod<Derived>::value, "");

Exemplo executável

Valentin Milea
fonte
Isso é bom, mas não vai funcionar se a função recebe nenhum argumento
Triskeldeian
Funciona muito bem. Não tive nenhum problema ao aplicar esse truque às funções membro sem argumentos.
JohnB
Isso funciona bem para mim com vários e sem argumentos de método, inclusive com sobrecargas e inclusive com herança, e com o uso de usingpara trazer sobrecargas da classe base. Funciona para mim no MSVC 2015 e com Clang-CL. No entanto, ele não funciona com o MSVC 2012.
Steveire 03/03/19
5

Você pode usar std :: is_member_function_pointer

class A {
   public:
     void foo() {};
}

 bool test = std::is_member_function_pointer<decltype(&A::foo)>::value;
Yochai Timmer
fonte
16
Não vai &A::fooser um erro de compilação se não há foonada em A? Eu li a pergunta original como sendo para trabalhar com qualquer classe de entrada, não apenas aquelas que têm algum tipo de membro nomeado foo.
11133 Jeff Walden
5

Eu vim com o mesmo tipo de problema e achei as soluções propostas aqui muito interessantes ... mas tinha o requisito de uma solução que:

  1. Detecta funções herdadas também;
  2. É compatível com compiladores não prontos para C ++ 11 (portanto, sem decltype)

Encontrei outro tópico propondo algo assim, com base em uma discussão do BOOST . Aqui está a generalização da solução proposta como uma declaração de duas macros para a classe de características, seguindo o modelo de classes boost :: has_ ​​* .

#include <boost/type_traits/is_class.hpp>
#include <boost/mpl/vector.hpp>

/// Has constant function
/** \param func_ret_type Function return type
    \param func_name Function name
    \param ... Variadic arguments are for the function parameters
*/
#define DECLARE_TRAITS_HAS_FUNC_C(func_ret_type, func_name, ...) \
    __DECLARE_TRAITS_HAS_FUNC(1, func_ret_type, func_name, ##__VA_ARGS__)

/// Has non-const function
/** \param func_ret_type Function return type
    \param func_name Function name
    \param ... Variadic arguments are for the function parameters
*/
#define DECLARE_TRAITS_HAS_FUNC(func_ret_type, func_name, ...) \
    __DECLARE_TRAITS_HAS_FUNC(0, func_ret_type, func_name, ##__VA_ARGS__)

// Traits content
#define __DECLARE_TRAITS_HAS_FUNC(func_const, func_ret_type, func_name, ...)  \
    template                                                                  \
    <   typename Type,                                                        \
        bool is_class = boost::is_class<Type>::value                          \
    >                                                                         \
    class has_func_ ## func_name;                                             \
    template<typename Type>                                                   \
    class has_func_ ## func_name<Type,false>                                  \
    {public:                                                                  \
        BOOST_STATIC_CONSTANT( bool, value = false );                         \
        typedef boost::false_type type;                                       \
    };                                                                        \
    template<typename Type>                                                   \
    class has_func_ ## func_name<Type,true>                                   \
    {   struct yes { char _foo; };                                            \
        struct no { yes _foo[2]; };                                           \
        struct Fallback                                                       \
        {   func_ret_type func_name( __VA_ARGS__ )                            \
                UTILITY_OPTIONAL(func_const,const) {}                         \
        };                                                                    \
        struct Derived : public Type, public Fallback {};                     \
        template <typename T, T t>  class Helper{};                           \
        template <typename U>                                                 \
        static no deduce(U*, Helper                                           \
            <   func_ret_type (Fallback::*)( __VA_ARGS__ )                    \
                    UTILITY_OPTIONAL(func_const,const),                       \
                &U::func_name                                                 \
            >* = 0                                                            \
        );                                                                    \
        static yes deduce(...);                                               \
    public:                                                                   \
        BOOST_STATIC_CONSTANT(                                                \
            bool,                                                             \
            value = sizeof(yes)                                               \
                == sizeof( deduce( static_cast<Derived*>(0) ) )               \
        );                                                                    \
        typedef ::boost::integral_constant<bool,value> type;                  \
        BOOST_STATIC_CONSTANT(bool, is_const = func_const);                   \
        typedef func_ret_type return_type;                                    \
        typedef ::boost::mpl::vector< __VA_ARGS__ > args_type;                \
    }

// Utility functions
#define UTILITY_OPTIONAL(condition, ...) UTILITY_INDIRECT_CALL( __UTILITY_OPTIONAL_ ## condition , ##__VA_ARGS__ )
#define UTILITY_INDIRECT_CALL(macro, ...) macro ( __VA_ARGS__ )
#define __UTILITY_OPTIONAL_0(...)
#define __UTILITY_OPTIONAL_1(...) __VA_ARGS__

Essas macros se expandem para uma classe de características com o seguinte protótipo:

template<class T>
class has_func_[func_name]
{
public:
    /// Function definition result value
    /** Tells if the tested function is defined for type T or not.
    */
    static const bool value = true | false;

    /// Function definition result type
    /** Type representing the value attribute usable in
        http://www.boost.org/doc/libs/1_53_0/libs/utility/enable_if.html
    */
    typedef boost::integral_constant<bool,value> type;

    /// Tested function constness indicator
    /** Indicates if the tested function is const or not.
        This value is not deduced, it is forced depending
        on the user call to one of the traits generators.
    */
    static const bool is_const = true | false;

    /// Tested function return type
    /** Indicates the return type of the tested function.
        This value is not deduced, it is forced depending
        on the user's arguments to the traits generators.
    */
    typedef func_ret_type return_type;

    /// Tested function arguments types
    /** Indicates the arguments types of the tested function.
        This value is not deduced, it is forced depending
        on the user's arguments to the traits generators.
    */
    typedef ::boost::mpl::vector< __VA_ARGS__ > args_type;
};

Então, qual é o uso típico que se pode fazer com isso?

// We enclose the traits class into
// a namespace to avoid collisions
namespace ns_0 {
    // Next line will declare the traits class
    // to detect the member function void foo(int,int) const
    DECLARE_TRAITS_HAS_FUNC_C(void, foo, int, int);
}

// we can use BOOST to help in using the traits
#include <boost/utility/enable_if.hpp>

// Here is a function that is active for types
// declaring the good member function
template<typename T> inline
typename boost::enable_if< ns_0::has_func_foo<T> >::type
foo_bar(const T &_this_, int a=0, int b=1)
{   _this_.foo(a,b);
}

// Here is a function that is active for types
// NOT declaring the good member function
template<typename T> inline
typename boost::disable_if< ns_0::has_func_foo<T> >::type
foo_bar(const T &_this_, int a=0, int b=1)
{   default_foo(_this_,a,b);
}

// Let us declare test types
struct empty
{
};
struct direct_foo
{
    void foo(int,int);
};
struct direct_const_foo
{
    void foo(int,int) const;
};
struct inherited_const_foo :
    public direct_const_foo
{
};

// Now anywhere in your code you can seamlessly use
// the foo_bar function on any object:
void test()
{
    int a;
    foo_bar(a); // calls default_foo

    empty b;
    foo_bar(b); // calls default_foo

    direct_foo c;
    foo_bar(c); // calls default_foo (member function is not const)

    direct_const_foo d;
    foo_bar(d); // calls d.foo (member function is const)

    inherited_const_foo e;
    foo_bar(e); // calls e.foo (inherited member function)
}
S. Paris
fonte
5

Para fazer isso, precisamos usar:

  1. Sobrecarga de modelo de função com diferentes tipos de retorno, dependendo se o método está disponível
  2. De acordo com as meta-condicionais no type_traitscabeçalho, queremos retornar uma true_typeoufalse_type de nossas sobrecargas
  3. Declare a true_typesobrecarga que espera um inte a false_typesobrecarga que espera que os parâmetros variáveis ​​explorem: "A prioridade mais baixa da conversão de reticências na resolução de sobrecarga"
  4. Ao definir a especificação do modelo para a true_typefunção, usaremos declvale decltypepermitiremos detectar a função independentemente das diferenças ou sobrecargas do tipo de retorno entre os métodos

Você pode ver um exemplo ao vivo disso aqui . Mas também vou explicar abaixo:

Eu quero verificar a existência de uma função chamada testque leva um tipo conversível int, então eu precisaria declarar essas duas funções:

template <typename T, typename S = decltype(declval<T>().test(declval<int>))> static true_type hasTest(int);
template <typename T> static false_type hasTest(...);
  • decltype(hasTest<a>(0))::valueis true(Observe que não há necessidade de criar funcionalidades especiais para lidar com a void a::test()sobrecarga, isso void a::test(int)é aceito)
  • decltype(hasTest<b>(0))::valueis true(Como o intconversível em double int b::test(double)é aceito, independentemente do tipo de retorno)
  • decltype(hasTest<c>(0))::valueis false( cnão possui um método nomeado testque aceita um tipo conversível a partir intdisso, isso não é aceito)

Esta solução tem 2 desvantagens:

  1. Requer uma declaração por método de um par de funções
  2. Cria poluição do espaço para nome, principalmente se quisermos testar nomes semelhantes, por exemplo, como nomearíamos uma função que desejasse testar um test()método?

Portanto, é importante que essas funções sejam declaradas em um namespace de detalhes ou, idealmente, se forem usadas apenas com uma classe, elas devem ser declaradas em particular por essa classe. Para esse fim, escrevi uma macro para ajudá-lo a abstrair essas informações:

#define FOO(FUNCTION, DEFINE) template <typename T, typename S = decltype(declval<T>().FUNCTION)> static true_type __ ## DEFINE(int); \
                              template <typename T> static false_type __ ## DEFINE(...); \
                              template <typename T> using DEFINE = decltype(__ ## DEFINE<T>(0));

Você pode usar isso como:

namespace details {
    FOO(test(declval<int>()), test_int)
    FOO(test(), test_void)
}

Chamar posteriormente details::test_int<a>::valueou details::test_void<a>::valuerenderia trueou falsepara os fins de código embutido ou metaprogramação.

Jonathan Mee
fonte
3

Para não ser intrusivo, você também pode colocar serializeno espaço para nome da classe que está sendo serializada ou da classe de arquivamento, graças à pesquisa Koenig . Consulte Namespaces para substituições gratuitas de funções para obter mais detalhes. :-)

Abrir qualquer espaço de nome para implementar uma função livre é Simplesmente Errado. (por exemplo, você não deve abrir um espaço stdpara nome para implementar swappara seus próprios tipos, mas deve usar a pesquisa Koenig.)

Chris Jester-Young
fonte
3

Você parece querer o idioma do detector. As respostas acima são variações sobre isso que funcionam com C ++ 11 ou C ++ 14.

A std::experimentalbiblioteca possui recursos que fazem essencialmente isso. Reformulando um exemplo acima, pode ser:

#include <experimental/type_traits>

// serialized_method_t is a detector type for T.serialize(int) const
template<typename T>
using serialized_method_t = decltype(std::declval<const T&>.serialize(std::declval<int>()));

// has_serialize_t is std::true_type when T.serialize(int) exists,
// and false otherwise.
template<typename T>
using has_serialize_t = std::experimental::is_detected_t<serialized_method_t, T>;

Se você não pode usar std :: experimental, uma versão rudimentar pode ser feita assim:

template <typename... Ts>
using void_t = void;
template <template <class...> class Trait, class AlwaysVoid, class... Args>
struct detector : std::false_type {};
template <template <class...> class Trait, class... Args>
struct detector<Trait, void_t<Trait<Args...>>, Args...> : std::true_type {};

// serialized_method_t is a detector type for T.serialize(int) const
template<typename T>
using serialized_method_t = decltype(std::declval<const T&>.serialize(std::declval<int>()));

// has_serialize_t is std::true_type when T.serialize(int) exists,
// and false otherwise.
template <typename T>
using has_serialize_t = typename detector<serialized_method_t, void, T>::type;

Como has_serialize_t é realmente std :: true_type ou std :: false_type, ele pode ser usado através de qualquer um dos idiomas comuns do SFINAE:

template<class T>
std::enable_if_t<has_serialize_t<T>::value, std::string>
SerializeToString(const T& t) {
}

Ou usando o despacho com resolução de sobrecarga:

template<class T>
std::string SerializeImpl(std::true_type, const T& t) {
  // call serialize here.
}

template<class T>
std::string SerializeImpl(std::false_type, const T& t) {
  // do something else here.
}

template<class T>
std::string Serialize(const T& t) {
  return SerializeImpl(has_serialize_t<T>{}, t);
}
lar
fonte
2

OK. Segunda tentativa. Tudo bem se você também não gosta deste, estou procurando mais idéias.

O artigo de Herb Sutter fala sobre traços. Portanto, você pode ter uma classe de características cuja instanciação padrão tem o comportamento de fallback e, para cada classe em que sua função de membro existe, a classe de características é especializada para chamar a função de membro. Acredito que o artigo de Herb menciona uma técnica para fazer isso, para que não envolva muitas cópias e colagens.

Como eu disse, talvez você não queira o trabalho extra envolvido com as classes de "marcação" que implementam esse membro. Nesse caso, estou procurando uma terceira solução ....

Chris Jester-Young
fonte
eh ... analisei essa solução ... acho que é um pouco cara demais para os usuários do meu framework. (ok, eu admito, eu estou desenvolvendo um quadro de streaming e eu estou escolhendo entre estendendo iostream ou reescrever algo mais fácil)
ugasoft
Minha terceira solução seria usar SFINAE. Como a resposta do yrp já o menciona, não vou entrar nisso (porque ainda estou pesquisando: conheço a ideia, mas o diabo está nos detalhes), a menos que a solução dele não funcione para você no final . :-)
Chris Jester-Young
1

Sem o suporte ao C ++ 11 ( decltype), isso pode funcionar:

SSCCE

#include <iostream>
using namespace std;

struct A { void foo(void); };
struct Aa: public A { };
struct B { };

struct retA { int foo(void); };
struct argA { void foo(double); };
struct constA { void foo(void) const; };
struct varA { int foo; };

template<typename T>
struct FooFinder {
    typedef char true_type[1];
    typedef char false_type[2];

    template<int>
    struct TypeSink;

    template<class U>
    static true_type &match(U);

    template<class U>
    static true_type &test(TypeSink<sizeof( matchType<void (U::*)(void)>( &U::foo ) )> *);

    template<class U>
    static false_type &test(...);

    enum { value = (sizeof(test<T>(0, 0)) == sizeof(true_type)) };
};

int main() {
    cout << FooFinder<A>::value << endl;
    cout << FooFinder<Aa>::value << endl;
    cout << FooFinder<B>::value << endl;

    cout << FooFinder<retA>::value << endl;
    cout << FooFinder<argA>::value << endl;
    cout << FooFinder<constA>::value << endl;
    cout << FooFinder<varA>::value << endl;
}

Espero que funcione

A, Aae Bsão as classes em questão, Aasendo a especial que herda o membro que procuramos.

No FooFindero true_typee false_typesão os substitutos para o correspondente C ++ 11 classes. Também para o entendimento da meta programação de modelos, eles revelam a própria base do truque do tamanho da SFINAE.

A TypeSinké uma estrutura de modelo que é usado mais tarde para afundar o resultado integrante do sizeofoperador para uma instanciação modelo, para formar um tipo.

A matchfunção é outro tipo de modelo SFINAE que é deixado sem uma contraparte genérica. Portanto, ele só pode ser instanciado se o tipo de argumento corresponder ao tipo para o qual foi especializado.

Ambas as testfunções, juntamente com a declaração enum, finalmente formam o padrão central do SFINAE. Há um genérico usando reticências que retornam false_typee uma contraparte com argumentos mais específicos para ter precedência.

Para poder instanciar a testfunção com um argumento de modelo de T, a matchfunção deve ser instanciada, pois seu tipo de retorno é necessário para instanciar o TypeSinkargumento. A ressalva é que &U::foo, sendo envolvido em um argumento de função, não é referido a partir de uma especialização de argumento de modelo, portanto a pesquisa de membro herdada ainda ocorre.

Kamajii
fonte
1

Se você estiver usando a loucura do Facebook, a macro está pronta para ajudá-lo:

#include <folly/Traits.h>
namespace {
  FOLLY_CREATE_HAS_MEMBER_FN_TRAITS(has_test_traits, test);
} // unnamed-namespace

void some_func() {
  cout << "Does class Foo have a member int test() const? "
    << boolalpha << has_test_traits<Foo, int() const>::value;
}

Embora os detalhes da implementação sejam os mesmos da resposta anterior, o uso de uma biblioteca é mais simples.

prehistoricpenguin
fonte
0

Eu tive uma necessidade semelhante e me deparei com este SO. Existem muitas soluções interessantes / poderosas propostas aqui, embora seja um pouco longo apenas para uma necessidade específica: detectar se uma classe tem função de membro com uma assinatura precisa. Então, eu fiz algumas leituras / testes e criei minha versão que poderia ser interessante. Detecta:

  • função membro estática
  • função membro não estática
  • função de membro não estático const

com uma assinatura precisa. Como não preciso capturar nenhuma assinatura (isso exigiria uma solução mais complicada), essa é a minha suíte. Ele basicamente usou o enable_if_t .

struct Foo{ static int sum(int, const double&){return 0;} };
struct Bar{ int calc(int, const double&) {return 1;} };
struct BarConst{ int calc(int, const double&) const {return 1;} };

// Note : second typename can be void or anything, as long as it is consistent with the result of enable_if_t
template<typename T, typename = T> struct has_static_sum : std::false_type {};
template<typename T>
struct has_static_sum<typename T,
                        std::enable_if_t<std::is_same<decltype(T::sum), int(int, const double&)>::value,T> 
                      > : std::true_type {};

template<typename T, typename = T> struct has_calc : std::false_type {};
template<typename T>
struct has_calc <typename T,
                  std::enable_if_t<std::is_same<decltype(&T::calc), int(T::*)(int, const double&)>::value,T>
                > : std::true_type {};

template<typename T, typename = T> struct has_calc_const : std::false_type {};
template<typename T>
struct has_calc_const <typename T,
                        std::enable_if_t<std::is_same<decltype(&T::calc), int(T::*)(int, const double&) const>::value,T>
                      > : std::true_type {};

int main ()
{
    constexpr bool has_sum_val = has_static_sum<Foo>::value;
    constexpr bool not_has_sum_val = !has_static_sum<Bar>::value;

    constexpr bool has_calc_val = has_calc<Bar>::value;
    constexpr bool not_has_calc_val = !has_calc<Foo>::value;

    constexpr bool has_calc_const_val = has_calc_const<BarConst>::value;
    constexpr bool not_has_calc_const_val = !has_calc_const<Bar>::value;

    std::cout<< "           has_sum_val " << has_sum_val            << std::endl
             << "       not_has_sum_val " << not_has_sum_val        << std::endl
             << "          has_calc_val " << has_calc_val           << std::endl
             << "      not_has_calc_val " << not_has_calc_val       << std::endl
             << "    has_calc_const_val " << has_calc_const_val     << std::endl
             << "not_has_calc_const_val " << not_has_calc_const_val << std::endl;
}

Resultado :

           has_sum_val 1
       not_has_sum_val 1
          has_calc_val 1
      not_has_calc_val 1
    has_calc_const_val 1
not_has_calc_const_val 1
ctNGUYEN
fonte
0

Com base JROK 's resposta , eu ter evitado o uso de classes e / ou funções modelo aninhado.

#include <type_traits>

#define CHECK_NESTED_FUNC(fName) \
    template <typename, typename, typename = std::void_t<>> \
    struct _has_##fName \
    : public std::false_type {}; \
    \
    template <typename Class, typename Ret, typename... Args> \
    struct _has_##fName<Class, Ret(Args...), \
        std::void_t<decltype(std::declval<Class>().fName(std::declval<Args>()...))>> \
    : public std::is_same<decltype(std::declval<Class>().fName(std::declval<Args>()...)), Ret> \
    {}; \
    \
    template <typename Class, typename Signature> \
    using has_##fName = _has_##fName<Class, Signature>;

#define HAS_NESTED_FUNC(Class, Func, Signature) has_##Func<Class, Signature>::value

Podemos usar as macros acima como abaixo:

class Foo
{
public:
    void Bar(int, const char *) {}
};

CHECK_NESTED_FUNC(Bar);  // generate required metafunctions

int main()
{
    using namespace std;
    cout << boolalpha
         << HAS_NESTED_FUNC(Foo, Bar, void(int, const char *))  // prints true
         << endl;
    return 0;
}

Sugestões são bem vindas.

debashish.ghosh
fonte