2012-06-13 10 views
5

Encontré preguntas y respuestas similares como this one. Sin embargo, cuando probé, estas pruebas SFINAE solo tuvieron éxito si el miembro evaluado se define directamente en la clase que se está probando. Por ejemplo, la siguiente clase B, D1 imprime HAS mientras que las otras dos imprimen NOT HAS. ¿Hay alguna forma de determinar si una clase tiene un miembro, si está definida por sí misma o una clase base, y el nombre de la clase base no se conoce en este caso? La motivación es que quiero escribir una función genérica que invoque un método determinado, si existe (desde la base o no, el tipo del parámetro es genérico, deje el tipo de su posible base).¿Es posible verificar si una función miembro está definida para una clase incluso si el miembro se hereda de una clase base desconocida?

#include <iostream> 

class HasFoo 
{ 
    public : 

    typedef char Small; 
    typedef struct {char; char;} Large; 

    template <typename C, void (C::*)()> class SFINAE {}; 

    template <typename C> static Small test (SFINAE<C, &C::foo> *) 
    { 
     std::cout << "HAS" << std::endl; 
    } 

    template <typename C> static Large test (...) 
    { 
     std::cout << "NOT HAS" << std::endl; 
    } 
}; 

class B 
{ 
    public : 

    void foo() {} 
}; 

class D1 : public B 
{ 
    public : 

    void foo() {} // overide 
}; 

class D2 : public B 
{ 
    public : 

    using B::foo; 
}; 

class D3 : public B {}; 

int main() 
{ 
    HasFoo::test<B>(0); 
    HasFoo::test<D1>(0); 
    HasFoo::test<D2>(0); 
    HasFoo::test<D3>(0); 
} 

Respuesta

3

Lamentablemente no sería posible al menos en C++ 03 y dudo en C++ 11 también.

algunos puntos importantes:

  1. El SFINAE propuesta sólo funciona si el método es public
  2. Incluso si el SFINAE hubiera funcionado para los métodos de base, el punto (1) se aplica; porque para private y protected herencia del SFINAE puede terminar inútil
  3. Suponiendo que es posible que desee tratar sólo con public método/herencia, el código HasFoo::test<> se puede mejorar para la toma de múltiples parámetros donde una clase base también se puede transmitir; std::is_base_of<> se puede utilizar para una mayor validación de la relación base/derivada ; luego aplique la misma lógica para la clase base también
+0

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 –

+0

'El SFINAE propuesto funciona solo si el método es público' ... ¿Por qué? – Nawaz

+0

@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

8

En C++ 03, lamentablemente esto no es posible, lo siento.

En C++ 11, las cosas obtienen mucho más fácil gracias a la magia de decltype. decltype le permite escribir expresiones para deducir el tipo de resultado, por lo que puede nombrar perfectamente a un miembro de una clase base. Y si el método es plantilla, entonces SFINAE se aplica a la expresión decltype.

#include <iostream> 

template <typename T> 
auto has_foo(T& t) -> decltype(t.foo(), bool()) { return true; } 

bool has_foo(...) { return false; } 


struct Base { 
    void foo() {} 
}; 

struct Derived1: Base { 
    void foo() {} 
}; 

struct Derived2: Base { 
    using Base::foo; 
}; 

struct Derived3: Base { 
}; 

int main() { 
    Base b; Derived1 d1; Derived2 d2; Derived3 d3; 

    std::cout << has_foo(b) << " " 
       << has_foo(d1) << " " 
       << has_foo(d2) << " " 
       << has_foo(d3) << "\n"; 
} 

Desafortunadamente Ideone tiene una versión de gcc que es demasiado viejo para esto y cling 3.0 no es mejor.

3

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.

Cuestiones relacionadas