Ejecutar la función dentro de la plantilla de función solo para aquellos tipos que tienen la función definida

13

Tengo una plantilla de función que toma muchos tipos diferentes como entrada. De esos tipos, solo uno de ellos tiene una getInt()función. Por lo tanto, quiero que el código ejecute la función solo para ese tipo. Por favor sugiera una solución. Gracias

#include <type_traits>
#include <typeinfo>

class X {
    public:
    int getInt(){
        return 9;
    }
};

class Y{

};

template<typename T>
void f(T& v){
    // error: 'class Y' has no member named 'getInt'
    // also tried std::is_same<T, X>::value 
    if(typeid(T).name() == typeid(X).name()){
        int i = v.getInt();// I want this to be called for X only
    }
}

int main(){
    Y y;
    f(y);
}
Sumit
fuente
No está relacionado con su problema, pero la type_infoestructura tiene un operador de comparación de igualdad , por lo typeid(T) == typeid(X)que también debería funcionar.
Algún tipo programador
55
Uso: if constexprcon condición is_same_v<T,X>.
rafix07
La solución a esto será oficialmente más elegante a finales de este año con Concepts. No es súper útil en este momento, lo sé.
sweenish
Hay muchas formas de resolver su problema. Una pareja mencionada anteriormente. También podría usar rasgos de diferentes variantes para ver si un tipo tiene un getIntmiembro invocable . Debe haber bastantes preguntas aquí en stackoverflow.com solo sobre cómo ver si una estructura o clase tiene una función miembro específica, si solo busca un poco.
Algún tipo programador
1
stackoverflow
idclev 463035818

Respuestas:

10

Si desea poder llamar a una función fpara todos los tipos que tienen miembro de función getInt, no solo X, puede declarar 2 sobrecargas para la función f:

  1. para los tipos que tienen getIntfunción miembro, incluida la claseX

  2. para todos los demás tipos, incluida la clase Y.

Solución C ++ 11 / C ++ 17

Teniendo eso en mente, podrías hacer algo como esto:

#include <iostream>
#include <type_traits>

template <typename, typename = void>
struct has_getInt : std::false_type {};

template <typename T>
struct has_getInt<T, std::void_t<decltype(((T*)nullptr)->getInt())>> : std::is_convertible<decltype(((T*)nullptr)->getInt()), int>
{};

class X {
public:
    int getInt(){
        return 9;
    }
};

class Y {};

template <typename T,
          typename std::enable_if<!has_getInt<T>::value, T>::type* = nullptr>
void f(T& v) {
    // only for Y
    std::cout << "Y" << std::endl;
}

template <typename T,
          typename std::enable_if<has_getInt<T>::value, T>::type* = nullptr>
void f(T& v){
    // only for X
    int i = v.getInt();
    std::cout << "X" << std::endl;
}

int main() {
    X x;
    f(x);

    Y y;
    f(y);
}

Compruébalo en vivo .

Tenga en cuenta que std::void_tse introdujo en C ++ 17, pero si está limitado a C ++ 11, entonces es realmente fácil de implementar void_tpor su cuenta:

template <typename...>
using void_t = void;

Y aquí está la versión C ++ 11 en vivo .

¿Qué tenemos en C ++ 20?

C ++ 20 trae muchas cosas buenas y una de ellas son los conceptos . Lo anterior que es válido para C ++ 11 / C ++ 14 / C ++ 17 se puede reducir significativamente en C ++ 20:

#include <iostream>
#include <concepts>

template<typename T>
concept HasGetInt = requires (T& v) { { v.getInt() } -> std::convertible_to<int>; };

class X {
public:
    int getInt(){
        return 9;
    }
};

class Y {};

template <typename T>
void f(T& v) {
    // only for Y
    std::cout << "Y" << std::endl;
}

template <HasGetInt T>
void f(T& v){
    // only for X
    int i = v.getInt();
    std::cout << "X" << std::endl;
}

int main() {
    X x;
    f(x);

    Y y;
    f(y);
}

Compruébalo en vivo .

Cascanueces
fuente
Sin void_tembargo, antes de C ++ 17, esa implementación causa problemas a algún compilador antiguo (como lo señala el enlace).
Jarod42
no es estrictamente necesario escribir dos sobrecargas (reemplazar "necesidad" con "lata" sería mucho mejor en mi humilde opinión)
idclev 463035818
@ idclev463035818 actualizado. Gracias
NutCracker
1
@SSAnne actualizado
NutCracker
1
La definición del concepto no es precisa. está asignando el resultado a un int por lo que el concepto debería sertemplate<typename T> concept HasGetInt = requires (T& v) { {v.getInt()} -> std::convertible_to<int>; };
Hui
8

Puede usar if constexprdesde C ++ 17:

template<typename T>
void f(T& v){
    if constexpr(std::is_same_v<T, X>) { // Or better create trait has_getInt
        int i = v.getInt();// I want this to be called for X only
    }
    // ...
}

Antes, tendrá que usar sobrecargas y SFINAE o envío de etiquetas.

Jarod42
fuente
if constexpres una característica de C ++ 17.
Andrey Semashev
Sin embargo, esto funcionaría solo para la claseX
NutCracker
La pregunta ahora se actualiza solo a C ++ 11 / C ++ 14
NutCracker
@NutCracker: No es bueno actualizar la etiqueta / pregunta e invalidar las respuestas existentes ... (incluso si la advertencia al respecto está bien).
Jarod42
acabo de actualizar la etiqueta ... OP título fue actualizado por OP
NutCracker
7

Mantenlo simple y sobrecargado. Ha trabajado desde al menos C ++ 98 ...

template<typename T>
void f(T& v)
{
    // do whatever
}

void f(X& v)
{
    int result = v.getInt();
}

Esto es suficiente si solo hay un tipo con getIntfunción. Si hay más, ya no es tan simple. Hay varias formas de hacerlo, aquí hay una:

struct PriorityA { };
struct PriorityB : PriorityA { };

template<typename T>
void f_impl(T& t, PriorityA)
{
    // generic version
}

// use expression SFINAE (-> decltype part)
// to enable/disable this overload
template<typename T>
auto f_impl(T& t, PriorityB) -> decltype(t.getInt(), void())
{
    t.getInt();
}

template<typename T>
void f(T& t)
{
    f_impl(t, PriorityB{ } ); // this will select PriorityB overload if it exists in overload set
                              // otherwise PriorityB gets sliced to PriorityA and calls generic version
}

Ejemplo en vivo con salida de diagnóstico.

jrok
fuente
1
En este caso, esto funcionaría ya que solo hay una sobrecarga (para X), pero, si hubiera más tipos similares con miembro getInten el futuro, esta no es una buena práctica. Probablemente quieras notar eso
NutCracker
@NutCracker lo hizo.
jrok