Pergunta

Eu estou pedindo um truque modelo para detectar se uma classe tem uma função de membro específico 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 uma função de membro com uma assinatura particular, então o programa não irá compilar. Em meu problema que eu preciso fazer alguma coisa, se uma classe tem essa função, senão fazer "algo mais".

Um problema semelhante foi enfrentado por boost :: serialização, mas eu não gosto da solução adotaram: a função de modelo que invoca, por padrão, uma função livre (que você tem que definir) com uma assinatura específica a menos que você definir um determinado função de membro (no caso deles "serialize" que leva 2 parâmetros de um determinado tipo) com uma assinatura particular, então um erro de compilação vai acontecer. Isso é implementar tanto intrusiva e serialização não intrusiva.

Eu não gosto dessa solução por duas razões:

  1. Para ser não intrusiva você deve substituir a função global "serialize" que está em namespace boost :: serialização, assim que você tem no seu código de cliente para impulsionar namespace aberto e serialização namespace!
  2. A pilha para resolver essa bagunça foi de 10 a 12 invocações de função.

Eu preciso definir um comportamento personalizado para as classes que não tem essa função de membro, e os meus entidades estão dentro diferentes namespaces (e eu não quero substituir uma função global definido em um namespace, enquanto eu estou em outro)

Você pode me dar uma dica para resolver este enigma?

Foi útil?

Solução

Eu não tenho certeza se eu entendi corretamente, mas você pode explorar SFINAE para detectar a presença de função em tempo de compilação. Exemplo do meu código (testes se tem classe membro used_memory função size_t () 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>());
}

Outras dicas

Aqui está uma possível implementação contando com C ++ 11 características. -Lo corretamente detecta a função mesmo que seja herdada (ao contrário da solução na resposta aceita, como Mike Kinghan observa na sua resposta ) .

A função desta testes trecho para é chamado 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

A resposta aceita a esta pergunta de compiletime-função-membro introspecção, embora seja justamente popular, tem um obstáculo que pode ser observado o 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, o programa saídas 110 - informando-nos que T = std::shared_ptr<int> não fornecer int & T::operator*() const.

Se você ainda não é sábio para este gotcha, em seguida, um olhar para a definição de std::shared_ptr<T> no <memory> cabeçalho irão lançar luz. Naquilo aplicação, std::shared_ptr<T> é derivado de uma classe de base a partir do qual ela herda operator*() const. Assim, a instanciação de modelo SFINAE<U, &U::operator*> que constitui "encontrar" o operador de U = std::shared_ptr<T> não vai acontecer, porque std::shared_ptr<T> não tem operator*() em seu próprio direito e instanciação modelo não "Fazer herança".

Esta senão não afeta a abordagem SFINAE bem conhecido, usando "O sizeof Trick ()", para a detecção apenas se T tem alguma função membro mf (veja-se e.g. este resposta e comentários). Mas estabelecendo que existe T::mf muitas vezes não é bom o suficiente (geralmente?): você pode Também é necessário estabelecer que tem uma assinatura desejado. É aí que a pontuações técnica ilustrada. A variante pointerized da assinatura desejada está inscrito em um parâmetro de um tipo de modelo que devem ser satisfeitas por &T::mf para a sonda SFINAE para ter sucesso. Mas esse modelo instanciar técnica dá a resposta errada quando T::mf é herdada.

Uma técnica SFINAE seguro para compiletime introspecção de T::mf deve evitar a usar de &T::mf dentro de um argumento de modelo para instanciar um tipo sobre o qual SFINAE resolução modelo de função depende. Em vez disso, função de modelo SFINAE resolução pode depender somente em declarações de tipo exatamente pertinentes usados como tipos de argumento da função sonda SFINAE sobrecarregado.

Por meio de uma resposta para a pergunta que habita por essa restrição eu vou para ilustrar compiletime detecção de E T::operator*() const, para T arbitrária e E. O mesmo padrão se aplicará mutatis mutandis a sonda para qualquer outra assinatura do 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 test() sobrecarregado é "invocado . Recursiva"(É claro que não é realmente invocado em tudo, ele simplesmente tem os tipos de retorno de invocações hipotéticos resolvidos pelo compilador.)

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

    exist T::operator*()
  • Será que em tudo? Se não, estamos a fazer.
  • Tendo em conta que existe T::operator*(), é a sua assinatura E T::operator*() const?

Nós obter 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;

Esta chamada pode ser resolvido para a sobrecarga /* SFINAE operator-exists :) */ de test(), ou pode resolver à sobrecarga /* SFINAE game over :( */. Ele não pode resolver à sobrecarga /* SFINAE operator-has-correct-sig :) */, porque que uma espera apenas um argumento e estamos passando dois.

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

Esta chamada para test(0,0) irá resolver a /* SFINAE operator-exists :) */ apenas no caso do primeiro argumento 0 satifies o primeiro tipo de parâmetro de que a sobrecarga, que é decltype(&A::operator*), com A = T. 0 irá satisfazer esse tipo apenas no caso existe T::operator*.

Vamos supor Sim, o compilador de dizer a isso. Então ele vai com /* SFINAE operator-exists :) */ e ele precisa para determinar o tipo de retorno a chamada de função, que nesse caso é decltype(test(&A::operator*)) - o retTipo de urna da outra chamada para test().

Desta vez, nós estamos passando apenas um argumento, &A::operator*, que agora sabe que existe, ou não estaríamos aqui. Uma chamada para poder test(&A::operator*) determinação quer para /* SFINAE operator-has-correct-sig :) */ ou novamente para pode resolver a /* SFINAE game over :( */. A chamada irá corresponder /* SFINAE operator-has-correct-sig :) */ apenas no satisfaz &A::operator* caso o único tipo de parâmetro de que a sobrecarga, que é E (A::*)() const, com A = T.

O compilador vai dizer sim aqui se T::operator* tem essa assinatura desejado, e depois novamente tem que avaliar o tipo de retorno da sobrecarga. Não mais "Recursions" agora:. É std::true_type

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

Aqui está um programa de teste que mostra o modelo produzindo o esperado respostas na 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 neste idéia? ele pode ser feito mais genérico, sem mais uma vez caindo em desgraça com o impedimento evita?

Aqui estão alguns trechos de uso: * A coragem para tudo isso são mais para baixo

Verificar x membro de uma determinada classe. Poderia ser var, func, classe, união, ou enum:

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

Verificar função 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;

Verificar x variável de membro:

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

Verificar x classe membro:

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

Verificar x união membro:

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

Verificar x membro enum:

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

Verifique se há qualquer função membro x independentemente 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)

Isso deve ser suficiente, se você souber o nome da função de membro que você está esperando. (Neste caso, a função bla não instanciar se não houver nenhuma função membro (escrevendo um que funcione de qualquer maneira é difícil porque há uma falta de função de especialização parcial. Você pode precisar usar modelos de classe) Além disso, a estrutura permitir (que é semelhante ao enable_if) também poderia ser templated do tipo de função que você deseja que ele tem como um 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);
}

Aqui está uma visão mais simples da resposta do Mike Kinghan. Isto irá detectar métodos herdados. Ele também irá verificar o exata assinatura (ao contrário abordagem da JROK que permite conversões argumento).

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

Você pode usar std :: is_member_function_pointer

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

 bool test = std::is_member_function_pointer<decltype(&A::foo)>::value;

Veio com o mesmo tipo de problema sozinho, e encontrou as soluções propostas aqui muito interessante ... mas tinha a necessidade de uma solução que:

  1. Detecta funções herdou também;
  2. É compatível com não C ++ 11 compiladores prontos (por isso não decltype)

Encontrado outro segmento propondo algo assim, com base em uma rEFORÇO discussão . Aqui é a generalização da solução proposta como declaração de duas macros para a classe características, seguindo o modelo de boost :: has_ ??* classes.

#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__

Estas macros expandir-se para uma classe traços 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, o que é o típico uso 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)
}

Para isso vamos precisar de usar:

  1. modelo de sobrecarga de funções com diferentes tipos de retorno, consoante a método está disponível
  2. Em consonância com os meta-condicionais no type_traits cabeçalho, nós vai querer voltar a true_type ou false_type dos nossos sobrecargas
  3. Declare a sobrecarga true_type esperando uma int ea sobrecarga false_type esperando variádicos Parâmetros para explorar: " a prioridade mais baixa da conversão reticências na resolução de sobrecarga "
  4. Na definição da especificação modelo para a função true_type usaremos declval e decltype nos permite detectar a função independente das diferenças de tipo de retorno ou sobrecargas entre métodos

Você pode ver um exemplo vivo desta aqui . , mas eu vou também explicá-lo abaixo:

Eu quero verificar a existência de uma função test chamado que leva um tipo conversível de int, então eu preciso 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))::value é true (note que não há necessidade de criar uma funcionalidade especial para lidar com a sobrecarga void a::test(), o void a::test(int) é aceito)
  • decltype(hasTest<b>(0))::value é true (Porque int é convertível para double int b::test(double) for aceita, independente do tipo de retorno)
  • decltype(hasTest<c>(0))::value é false (c não tem um método test nomeado que aceita um tipo conversível de int mesmos esta não é aceite)

Esta solução tem 2 inconvenientes:

  1. Requer uma declaração por método de um par de funções
  2. Cria poluição namespace especialmente se queremos teste para nomes semelhantes, por exemplo, o que chamamos uma função que queria teste para um método test()?

Portanto, é importante que estas funções ser declaradas em um detalhes namespace, ou, idealmente, se eles são apenas para ser usado com uma classe, eles devem ser declarados em particular por essa classe. Para o efeito Eu escrevi uma macro para ajudá-lo abstrato esta informação:

#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)
}

Em seguida chamando details::test_int<a>::value ou details::test_void<a>::value renderia true ou false para fins de código embutido ou meta-programação.

Para ser não-intrusiva, você também pode colocar serialize no espaço de nomes da classe a ser serializado, ou da classe de arquivo, graças a Koenig pesquisa . Consulte Namespaces para a função Livre substituições para mais detalhes. : -)

A abertura de qualquer namespace para implementar uma função livre é simplesmente errada. (Por exemplo, você não deveria se abrir namespace std para implementar swap para seus próprios tipos, mas deve usar Koenig pesquisa em vez.)

Ok. Segunda tentativa. Tudo bem se você não como este, quer, eu estou procurando mais idéias.

O artigo de Herb Sutter fala sobre traços. Então você pode ter uma classe traços cujo padrão instanciação tem o comportamento fallback, e para cada classe onde existe a sua função de membro, então a classe características é especializada para invocar a função de membro. Eu acredito que o artigo de Herb menciona uma técnica para fazer isso para que não envolve muita copiando e colando.

Como eu disse, no entanto, talvez você não quer que o trabalho extra envolvido com "marcação" classes que fazem implementar esse membro. Nesse caso, eu estou olhando para uma terceira solução ....

Sem C ++ 11 suporte (decltype) este trabalho poder:

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;
}

Como espero que funciona

A, Aa e B são as clases em questão, Aa sendo o especial que herda o membro que estamos procurando.

No FooFinder o true_type e false_type são os substitutos para o correspondente C ++ 11 classes. Também para a compreensão da programação modelo de meta, eles revelam a própria base do SFINAE-sizeof-trick.

O TypeSink é um struct modelo que é usado mais tarde para afundar o resultado integrante do operador sizeof em uma instanciação de modelo para formar um tipo.

A função match é um outro tipo SFINAE de modelo que é deixado sem uma contrapartida genérico. Ele pode, portanto, só pode ser instanciado se o tipo de seu argumento corresponde ao tipo que era especializada em.

funções

Ambos os test juntamente com a declaração enum finalmente formar o padrão central SFINAE. Há um genérico usando reticências que os retornos do false_type e uma contrapartida com mais argumentos específicos a ter precedência.

Para ser capaz de instanciar a função test com um argumento de modelo de T, a função match deve ser instanciado, como seu tipo de retorno é necessário para instanciar o argumento TypeSink. A ressalva é que &U::foo, estar envolvido em um argumento de função, é não referido a partir de uma especialização argumento modelo, lookup membro assim herdou ainda ocorre.

Eu acredito que a resposta que você está procurando aqui.

http://www.martinecker.com/wiki/index .php? title = Detecting_the_Existence_of_Operators_at_Compile-Time

e um exemplo um pouco mais preenchido aqui

http://pastie.org/298994

I utilizar a técnica para detectar a presença de um operador ostream apoio << sobre a classe em questão e, em seguida, gerar um pouco diferente do código de função.

Eu não acreditava que era possível antes de encontrar a solução ligada, mas é um truque muito puro. Passe o tempo a compreensão do código e é muito vale o tempo.

Brad

Se você estiver usando facebook loucura, sua estão fora da macro caixa 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 de implementação é o mesmo com a resposta anterior, use uma biblioteca é mais simples.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top