Hay una manera de determinar si una jerarquía de clase tiene un miembro de un nombre de pila. Utiliza SFINAE e introduce una falla de sustitución en la búsqueda de nombres creando una ambigüedad. Además, hay una manera de probar si los miembros públicos son invocables; sin embargo, no hay una forma de determinar si un miembro es público con SFINAE.
Here es un ejemplo:
#include <iostream>
template < typename T >
struct has_foo
{
typedef char yes;
typedef char no[2];
// Type that has a member with the name that will be checked.
struct fallback { int foo; };
// Type that will inherit from both T and mixin to guarantee that mixed_type
// has the desired member. If T::foo exists, then &mixed_type::foo will be
// ambiguous. Otherwise, if T::foo does not exists, then &mixed_type::foo
// will successfully resolve to fallback::foo.
struct mixed_type: T, fallback {};
template < typename U, U > struct type_check {};
// If substituation does not fail, then &U::foo is not ambiguous, indicating
// that mixed_type only has one member named foo (i.e. fallback::foo).
template < typename U > static no& test(type_check< int (fallback::*),
&U::foo >* = 0);
// Substituation failed, so &U::foo is ambiguous, indicating that mixed_type
// has multiple members named foo. Thus, T::foo exists.
template < typename U > static yes& test(...);
static const bool value = sizeof(yes) ==
sizeof(test<mixed_type>(NULL));
};
namespace detail {
class yes {};
class no{ yes m[2]; };
// sizeof will be used to determine what function is selected given an
// expression. An overloaded comma operator will be used to branch based
// on types at compile-time.
// With (helper, anything-other-than-no, yes) return yes.
// With (helper, no, yes) return no.
struct helper {};
// Return helper.
template < typename T > helper operator,(helper, const T&);
// Overloads.
yes operator,(helper, yes); // For (helper, yes) return yes.
no operator,(helper, no); // For (helper, no ) return no.
no operator,(no, yes); // For (no, yes) return no.
} // namespace detail
template < typename T >
struct can_call_foo
{
struct fallback { ::detail::no foo(...) const; };
// Type that will inherit from T and fallback, this guarantees
// that mixed_type has a foo method.
struct mixed_type: T, fallback
{
using T::foo;
using fallback::foo;
};
// U has a foo member.
template < typename U, bool = has_foo<U>::value >
struct impl
{
// Create the type sequence.
// - Start with helper to guarantee the custom comma operator is used.
// - This is evaluationg the expression, not executing, so cast null
// to a mixed_type pointer, then invoke foo. If T::foo is selected,
// then the comma operator returns helper. Otherwise, fooback::foo
// is selected, and the comma operator returns no.
// - Either helper or no was returned from the first comma operator
// evaluation. If (helper, yes) remains, then yes will be returned.
// Otherwise, (no, yes) remains; thus no will be returned.
static const bool value = sizeof(::detail::yes) ==
sizeof(::detail::helper(),
((mixed_type*)0)->foo(),
::detail::yes());
};
// U does not have a 'foo' member.
template < typename U >
struct impl< U, false >
{
static const bool value = false;
};
static const bool value = impl<T>::value;
};
// Types containing a foo member function.
struct B { void foo(); };
struct D1: B { bool foo(); }; // hide B::foo
struct D2: B { using B::foo; }; // no-op, as no hiding occured.
struct D3: B { };
// Type that do not have a member foo function.
struct F {};
// Type that has foo but it is not callable via T::foo().
struct G { int foo; };
struct G1 { bool foo(int); };
int main()
{
std::cout << "B: " << has_foo<B>::value << " - "
<< can_call_foo<B>::value << "\n"
<< "D1: " << has_foo<D1>::value << " - "
<< can_call_foo<D1>::value << "\n"
<< "D2: " << has_foo<D2>::value << " - "
<< can_call_foo<D2>::value << "\n"
<< "D3: " << has_foo<D3>::value << " - "
<< can_call_foo<D3>::value << "\n"
<< "F: " << has_foo<F>::value << " - "
<< can_call_foo<F>::value << "\n"
<< "G: " << has_foo<G>::value << " - "
<< can_call_foo<G>::value << "\n"
<< "G1: " << has_foo<G1>::value << " - "
<< can_call_foo<G1>::value << "\n"
<< std::endl;
return 0;
}
que produce el siguiente resultado:
B: 1 - 1
D1: 1 - 1
D2: 1 - 1
D3: 1 - 1
F: 0 - 0
G: 1 - 0
G1: 1 - 0
has_foo
sólo comprueba la existencia de un miembro denominado foo
. No verifica que foo
es un miembro invocable (una función miembro pública o un miembro público que es un funtor).
can_call_foo
comprueba si T::foo()
es exigible. Si T::foo()
no es público, se producirá un error de compilación. Hasta donde yo sé, no hay forma de evitar esto a través de SFINAE. Para una solución más completa y brillante, pero bastante compleja, marque here.
Gracias por la respuesta. Me di cuenta de eso mucho. El problema es que no sabré el caso base. De hecho, la clase base es una plantilla. La intención es que los usuarios no puedan usar mi clase base, sino que definan su propia clase con una interfaz compatible, mientras que parte de la interfaz es opcional. Además, las clases de usuarios pueden derivarse de otras bases que no puedo conocer al escribir la biblioteca. La solución ahora que uso es una clase de etiqueta vacía que es inherente a todas las clases para indicar que también tiene la parte opcional. Y luego el uso es la base de. Solo esperaba que Alguien supiera algo que yo no hago –
'El SFINAE propuesto funciona solo si el método es público' ... ¿Por qué? – Nawaz
@Nawaz, [errores de compilación] (http://ideone.com/ba1v5) después de hacer 'D1 :: foo()' como 'privado'. Se aplica también a los métodos "protegidos". – iammilind