Pergunta

É possível escrever um modelo que altere o comportamento dependendo se uma determinada função membro está definida em uma classe?

Aqui está um exemplo simples do que eu gostaria de escrever:

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

Então se class T tem toString() definido, então ele o utiliza;caso contrário, isso não acontece.A parte mágica que não sei fazer é a parte "FUNCTION_EXISTS".

Foi útil?

Solução

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 testá -lo com o Linux e o GCC 4.1/4.3. Não sei se é portátil para outras plataformas que executam diferentes compiladores.

Outras dicas

Esta pergunta é antiga, mas com C ++ 11, temos uma nova maneira de verificar uma existência de funções (ou existência de qualquer membro não do tipo, na verdade), dependendo de 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 em algumas explicações. Primeira coisa, eu uso expressão sfinae para excluir o serialize(_imp) funções da resolução de sobrecarga, se a primeira expressão dentro decltype não é válido (também conhecido como, a função não existe).

o void() é usado para fazer o tipo de retorno de todas essas funções void.

o 0 O argumento é usado para preferir o os << obj sobrecarga se ambos estiverem disponíveis (literal 0 é do tipo int e, como tal, a primeira sobrecarga é uma correspondência melhor).


Agora, você provavelmente deseja uma característica para verificar se existe uma função. Felizmente, é fácil escrever isso. Observe, porém, que você precisa escrever uma característica você mesma Para cada nome de função diferente que você deseja.

#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 é um tipo de ajudante e basicamente equivale a o mesmo que escrever decltype(void(std::declval<T>().stream(a0)), std::true_type{}). A vantagem é simplesmente que é mais curta.
Em seguida, o struct has_stream : decltype(...) herda de ambos std::true_type ou std::false_type no final, dependendo de se o decltype check-in test_stream falha ou não.
Último, std::declval Dá a você um "valor" de qualquer tipo que você passe, sem precisar saber como pode construí -lo. Observe que isso só é possível dentro de um contexto não avaliado, como decltype, sizeof e outros.


Observe que decltype não é necessariamente necessário, como sizeof (e todos os contextos não avaliados) obtiveram esse aprimoramento. É só isso decltype Já oferece um tipo e, como tal, é apenas mais limpo. Aqui está um sizeof versã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;
}

o int e long Os parâmetros ainda estão lá pelo mesmo motivo. O ponteiro da matriz é usado para fornecer um contexto onde sizeof pode ser usado.

C ++ permite Sfinae Para ser usado para isso (observe que, com os recursos C ++ 11, isso é mais simples, porque suporta sfinae estendido em expressões quase arbitrárias - o abaixo foi criado para trabalhar com compiladores comuns de 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 acima e a macro tenta instanciar um modelo, dando -lhe um tipo de ponteiro de função de membro e o ponteiro da função de membro real. Se os tipos não encaixam, o SFINAE faz 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 isso toString função nessa ramificação if. Como o compilador verá a validade nas duas ramificações, isso falharia nos casos a função não existe. Uma maneira é usar o SFINAE mais uma vez (Atable_if pode ser obtido do Boost também):

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 isso. A vantagem disso é que ele também funciona para funções de membros sobrecarregadas e também para funções de membro const (lembre -se de usar std::string(T::*)() const como o tipo de ponteiro da função de membro então!).

Embora essa pergunta tenha dois anos, ousarei adicionar minha resposta. Espero que ele esclareça a solução anterior, indiscutivelmente excelente. Tomei as respostas muito úteis de Nicola Bonelli e Johannes Schaub e as mesciei em uma solução que é, IMHO, mais legível, clara e não requer o typeof extensã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 vai principalmente para Nicola Bonelli e Johannes Schaub, então dê a eles uma votação se minha resposta o ajudar :)

C++20 - requires expressões

Com o C++20 vêm conceitos e diversas ferramentas, como requires expressões que são uma maneira integrada de verificar a existência de uma função.Com tehm você poderia reescrever seu optionalToString funcionar da seguinte forma:

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 takeit de detecção para inclusão na biblioteca padrão C++ 17 que pode resolver o problema de uma maneira um tanto elegante.Além disso, acabou de ser aceito nos fundamentos da biblioteca TS v2.Ele introduz algumas metafunções, incluindo std::is_detected que pode ser usado para escrever facilmente metafunções de detecção de tipo ou função na parte superior dele.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 em bibliotecas padrão, mas a proposta contém uma implementação completa que você pode copiar facilmente se realmente precisar.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";
}

Impulsionar.TTI

Outro kit de ferramentas um tanto idiomático para realizar tal verificação - embora menos elegante - é Impulsionar.TTI, introduzido no Boost 1.54.0.Para o seu exemplo, você teria que 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;

Então, você poderia usar o bool para criar uma verificação SFINAE.

Explicação

A macro BOOST_TTI_HAS_MEMBER_FUNCTION gera a metafunção has_member_function_toString que toma 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 parâmetros a seguir correspondem aos tipos dos parâmetros da função.O membro value contém true se a aula T tem uma função membro std::string toString().

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

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

É para isso que existem traços 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;
}

Bem, essa pergunta já tem uma longa lista de respostas, mas eu gostaria de enfatizar o comentário de Morwenn: há uma proposta para C ++ 17 que o torna realmente muito mais simples. Ver N4502 Para 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 da variável, onde você especifica o que está procurando (um tipo, um tipo de membro, uma função, uma função de membro etc.). No caso do OP:

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, tirado de N4502, mostra uma sonda 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, este é bastante simples: um conjunto reduzido de ferramentas (void_t e detect) Basta, não há necessidade de macros peludos. Além disso, foi relatado (ver N4502) que é mensurávelmente 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 Clang, mas infelizmente, as versões do GCC antes de 5.1 seguem uma interpretação diferente do padrão C ++ 11, que causou void_t não funcionar como esperado. Yakk já forneceu o trabalho: 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

Esta é uma solução C ++ 11 para o problema geral se "se eu fizesse X, isso compilaria?"

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

Característica has_to_string de tal modo que has_to_string<T>::value é true se e apenas se T tem um método .toString Isso pode ser invocado com 0 argumentos neste contexto.

Em seguida, eu usaria a despacho 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>{} );
}

o que tende a ser mais sustentável do que as expressões complexas de Sfinae.

Você pode escrever essas características com uma macro se estiver fazendo isso muito, mas elas são relativamente simples (algumas linhas cada), então talvez não valham 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 do traço que deseja e algum código que pode testar o tipo T. Desta forma:

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 o MS chama de "expressão sfinae", e seu compilador de 2013 falha bastante.

Observe que em C ++ 1y a seguinte sintaxe é possível:

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 de compilação em linha que abusa de muitos recursos C ++. Provavelmente, isso não vale a pena, pois o benefício (do código está embutido) não vale o custo (do próximo a ninguém entender como funciona), mas a existência dessa solução acima pode ser de interesse.

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

Verifique se há membro x em 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 do 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 para o membro enum x:

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

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 esta situação, refere -se a:

Em russo :http://www.rsdn.ru/forum/message/2759773.1.aspx

Tradução em inglês de Roman.perepelitsa:http://groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?pli=1

É incrivelmente inteligente. No entanto, um problema com essa solução é que dá a erros do compilador se o tipo que está sendo testado for aquele que não pode ser usado como uma classe base (por exemplo, tipos primitivos)

No Visual Studio, notei que, se trabalhe com o método sem argumentos, um par extra de redundante () precisa ser inserido em torno dos argumentos para deduzir () na expressão de tamanho de.

MSVC possui as palavras -chave __IF_Exists e __if_Not_Exists (Doc). Juntamente com a abordagem Typeof-Sfinae de Nicola, eu poderia criar uma verificação para o GCC e o MSVC como o OP procurado.

Atualizar: Fonte pode ser encontrada Aqui

Eu escrevi uma resposta para isso em outro tópico que (diferente das soluções acima) também verifica as funções de membro herdado:

Sfinae para verificar se há funções de membro herdado

Aqui estão algum exemplo dessa solução:

Exemplo 1:

Estamos verificando 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 mesmo verifica a constência do método e também funciona com tipos primitivos. (Quero dizer has_const_begin<int>::value é falso e não causa um erro de 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 construído padrão ou para satisfazer qualquer conceito especial. A técnica também trabalha com membros do modelo.

Estou ansiosamente esperando opiniões sobre isso.

Agora isso era um legais Pequeno quebra -cabeça - ótima pergunta!

Aqui está uma alternativa a Solução de Nicola Bonelli isso não depende do fora do padrão typeof operador.

Infelizmente, ele não funciona no GCC (Mingw) 3.4.5 ou no Marte Digital 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 funciona (ou deve funcionar). Como diz, não tenho certeza de qual comportamento é compatível com os padrões - eu daria boas -vindas a comentários 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 (graças a Leon Timmermans e litb por me apontar na direção certa). O padrão C ++ 03 diz o seguinte:

14.6.2 Nomes dependentes [temp.dep

Parágrafo 3

Na definição de um modelo de classe ou 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 será examinado durante a pesquisa de nome não qualificado no ponto de definição da classe modelo ou membro ou durante uma instanciação do modelo de classe ou membro.

Então, parece que quando MSVC ou Comeau consideram o toString() função de membro de T executando a pesquisa de nomes no site de chamada em doToString() Quando o modelo é instanciado, isso está incorreto (mesmo que seja realmente o comportamento que eu estava procurando neste caso).

O comportamento do GCC e do Marte digital parece estar correto - em ambos os casos, o não -membro toString() A função está ligada à chamada.

Ratos - eu pensei que poderia ter encontrado uma solução inteligente, em vez disso, descobri alguns bugs 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;
}

Eu modifiquei a solução fornecida em https://stackoverflow.com/a/264088/2712152 para torná -lo um pouco mais geral. Além disso, como ele não usa nenhum dos novos recursos C ++ 11, podemos usá -lo com compiladores antigos e também deve funcionar com o MSVC. Mas os compiladores devem permitir que o C99 use isso, pois usa macros variádicos.

A macro a seguir pode ser usada para verificar se uma classe específica tem um determinado typedef 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 determinado número 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 os cheques para 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;
}

Um exemplo usando a especialização parcial de Sfinae e modelo, escrevendo um Has_foo Verificaçã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");

Estranho ninguém sugeriu o seguinte truque bom 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 garantir que T seja uma aula. Parece que a ambiguidade na pesquisa de Foo é uma falha de substituição. Eu o fiz funcionar no GCC, não tenho certeza se é padrão.

O modelo genérico que pode ser usado para verificar se algum "recurso" for 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 foo que é compatível com a assinatura double(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

Há muitas respostas aqui, mas eu falhei, em encontrar uma versão, que execute real Ordem de resolução do método, sem usar nenhum dos recursos mais recentes do C ++ (usando apenas os recursos C ++ 98).
NOTA: Esta versão é testada e trabalhando com VC ++ 2013, G ++ 5.2.0 e o OnLline Compiler.

Então eu criei 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 do tipo de retorno estendido e solução alternativa VC ++ 2010): http://cpp.sh/5b2vs

Nenhuma fonte, como eu mesmo criei com isso.

Ao executar a demonstração ao vivo no compilador G ++, observe que os tamanhos de matriz de 0 são permitidos, o que significa que o static_assert usado não acionará um erro do compilador, mesmo quando falhar.
Um trabalho de trabalho comumente usado é substituir o 'typedef' na macro por 'extern'.

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

Aqui está minha versão que lida com todas as sobrecargas de função de membro possível com arity arbitrária, incluindo funções de membro do 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 determinados tipos de arg: (1) válido ou (2) ambíguo ou (3) não viá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 usar 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 portá-lo facilmente (com pequenos ajustes) para não C ++ 11 que possui extensões de tipo de (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

Você pode pular toda a metaprogramação em C ++ 14 e apenas escrever isso usando fit::conditional de Em forma biblioteca:

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 a partir dos 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()
);

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>* = nullptr permitirá a função que leva extra int argumento que tem uma prioridade sobre a função que leva long Quando chamado com 0.

Você pode usar o mesmo princípio para as funções que retornam true Se 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
}

Eu tive um problema parecido:

Uma classe de modelo que pode ser derivada de poucas classes base, algumas que têm um certo membro e outros que não.

Eu resolvi isso de maneira semelhante à resposta "TypeOf" (Nicola Bonelli), mas com o Decttype, para que ele compile e execute 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;
}
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!";
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top