2012-06-09 13 views
6

Editar: La respuesta corta a mi pregunta es que tuve una visión equivocada de lo que SFINAE puede hacer y no se compruebe el cuerpo de la función en absoluto: does sfinae instantiates a function body?¿Hay alguna manera de detectar si una función existe y se puede usar en tiempo de compilación?

Tengo un problema similar a éste: Is it possible to write a template to check for a function's existence?

La diferencia es que no solo quiero verificar si la función existe, sino también quiero saber si pasará SFINAE. Aquí está un ejemplo de lo que estoy tratando de lograr:

struct A 
{ 
    void FuncA() { std::cout << "A::FuncA" << std::endl; } 
}; 

struct B 
{ 
    void FuncA() { std::cout << "B::FuncA" << std::endl; } 
    void FuncB() { std::cout << "B::FuncB" << std::endl; } 
}; 

template<typename T> 
struct Inter 
{ 
    void FuncA() { t.FuncA(); } 
    void FuncB() { t.FuncB(); } 

    T t; 
}; 

// Always takes some sort of Inter<T>. 
template<typename InterType> 
struct Final 
{ 
    void CallFuncs() 
    { 
     // if(t.FuncA() exists and can be called) 
      t.FuncA(); 

     // if(t.FuncB() exists and can be called) 
      t.FuncB(); 
    } 

    InterType t; 
}; 

void DoEverything() 
{ 
    Final<Inter<A>> finalA; 
    Final<Inter<B>> finalB; 

    finalA.CallFuncs(); 
    finalB.CallFuncs(); 
} 

Nótese que en CallFuncs(), tanto funcA() y FuncB() siempre va a existir, pero no podrá recopilar dependiendo del tipo T utilizado en Inter. Cuando traté de usar la respuesta en la pregunta vinculada anterior, parecía darme siempre la verdad, lo que supongo es porque solo está comprobando que la función existe, no que realmente se pueda compilar (aunque no puedo descartar que yo no modifica algo ...)

con el fin de llamar condicionalmente las funciones Calculo que puedo utilizar enable_if como tal:

template<typename InterType> 
typename std::enable_if< ! /* how to determine if FuncA can be called? */>::type TryCallFuncA(InterType& i) 
{ 
} 
template<typename InterType> 
typename std::enable_if</* how to determine if FuncA can be called? */>::type TryCallFuncA(InterType& i) 
{ 
    i.FuncA(); 
} 

template<typename InterType> 
typename std::enable_if< ! /* how to determine if FuncB can be called? */>::type TryCallFuncB(InterType& i) 
{ 
} 
template<typename InterType> 
typename std::enable_if</* how to determine if FuncB can be called? */>::type TryCallFuncB(InterType& i) 
{ 
    i.FuncB(); 
} 

template<typename InterType> 
struct Final 
{ 
    void CallFuncs() 
    { 
     TryCallFuncA(t); 
     TryCallFuncB(t); 
    } 

    InterType t; 
}; 

pero no estoy seguro si hay cualquier manera que pueda obtener un valor booleano para pasar a enable_if. ¿Hay alguna forma de que pueda lograr esto o necesito recurrir a algún tipo de rasgos de tipo mantenidos manualmente que indiquen si las funciones existen?

Por lo que vale la pena en cuanto a la disposición C++ conjunto 11 de función, estoy usando MSVC 2010.

edición: Para añadir una nota importante, en mi situación real de la implementación de la clase Inter es efectivamente opaco en el punto donde necesito determinar si Inter :: FuncA/FuncB se compilará o no, así que no puedo simplemente crear los tipos secundarios y verificar la existencia de la función en ellos.

Respuesta

8

no tengo el tiempo para comprobar esto ahora, pero se puede añadir una especialización de Final:. template <typename T> struct Final< Inner<T> >; (que también ayuda a asegurar que el tipo es siempre un Inner Con que se puede extraer del tipo usado para crear instancias de Inter .

Ahora, el segundo problema es cómo utilizar SFINAE para detectar si existe una función miembro creo que esto no debería ser demasiado complejo (si es que no es necesario para hacer de este genérico):.

// Find out whether U has `void f()` member 
template <typename U> 
struct has_member_f { 
    typedef char yes; 
    struct no { char _[2]; }; 
    template<typename T, void (T::*)() = &T::f> 
    static yes impl(T*); 
    static no impl(...); 

    enum { value = sizeof(impl(static_cast<U*>(0))) == sizeof(yes) }; 
}; 

Es posible que pueda extender esto un poco para que sea un poco más ge neric, pero el nombre de la función no creo que puedas hacer genérico. Por supuesto, puede escribir eso como una macro que genera has_member_##arg y usa &T:: arg. El tipo de la pieza es probablemente más fácil de generalizar ...

alternativa, puesto que no creo que esto se puede hacer genérica, puede utilizar el truco en el interior has_member directamente en su tipo: proporcionar dos callFuncA sobrecargas, una plantilla con el segundo argumento opcional con la firma que desea y el valor predeterminado es &T::FuncA que reenvía la llamada, el otro con puntos suspensivos que es un noop. Luego, callFuncs llamaría al callFuncA y callFuncB, y SFINAE se enviará al reenviador o al mediodía y obtendrá el comportamiento deseado.

template<typename T> 
struct Final< Inter<T> > 
{ 
    template <typename U, void (U::*)() = &U::FuncA> 
    void callFuncA(Inter<T>* x) { 
     x.FuncA(); 
    } 
    void callFuncA(...) {} 

    void CallFuncs() { 
     callFuncA(&t);     // Cannot pass nonPOD types through ... 
     // Similarly TryCallFuncB(t); 
    } 
    Inter<T> t; 
}; 
+0

Si no recuerdo mal, existe un problema con las plantillas de argumentos y funciones por defecto en C++ 03 que harían que esta solución solo esté disponible en C++ 11. ¿Podrías confirmar/enfermar? –

+0

@MatthieuM .: Correcto, esta es una solución única de C++ 11. En C++ no puede proporcionar el argumento predeterminado para la plantilla de función. Esta es una característica muy buena que ha escapado al radar en el nuevo estándar y es necesaria en muchos casos donde el estándar exige que una función particular * no participe en la resolución de sobrecarga a menos que X sea verdadera. * (Es decir, SFINAE obligatoria). –

+0

Desafortunadamente eso no es compatible con VS (2010 o 2012). A los efectos de comprender la solución, el argumento predeterminado & U :: FuncA no solo se comprueba que existe, sino también que si se llama se compilará correctamente (por ejemplo, el tipo proporcionado para Inter podría ser una clase de plantilla (por ejemplo, estructura A ) y la implementación de FuncA/FuncB depende de ese tipo)? – Screndib

Cuestiones relacionadas