¿Comprobación con plantilla de la existencia de una función miembro de clase?

14 minutos de lectura

¿Comprobacion con plantilla de la existencia de una funcion miembro
andy

¿Es posible escribir una plantilla que cambie el comportamiento dependiendo de si una determinada función miembro está definida en una clase?

Aquí hay un ejemplo simple de lo que me gustaría escribir:

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

Así que si class T posee toString() definido, entonces lo usa; de lo contrario, no lo hace. La parte mágica que no sé cómo hacer es la parte “FUNCTION_EXISTS”.

  • Por supuesto, no hace falta decir que la(s) respuesta(s) de la plantilla a continuación solo funcionan con información en tiempo de compilación, es decir, T debe tener toString. Si pasa en una subclase de T que lo hace define toString, pero T lo hace nose le indicará que la cadena no está definida.

    -Alice Purcell

    2 de septiembre de 2010 a las 9:50

  • Posible duplicado ¿Cómo verificar si existe un nombre de miembro (variable o función) en una clase, con o sin especificar el tipo?, ya que cubre un problema más amplio con C++03 a C++1y.

    – iammilind

    18 de marzo de 2016 a las 9:13

  • Tenga en cuenta que C ++ 20 ahora permite hacer eso con conceptos. Consulte stackoverflow.com/questions/58394556/… y en.cppreference.com/w/cpp/language/constraints

    – etam

    2 de diciembre de 2021 a las 21:07

¿Comprobacion con plantilla de la existencia de una funcion miembro
Xeo

Esta pregunta es antigua, pero con C ++ 11 obtuvimos una nueva forma de verificar la existencia de funciones (o la existencia de cualquier miembro que no sea de tipo, en realidad), confiando nuevamente en SFINAE:

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

Ahora en algunas explicaciones. Lo primero que uso expresión SFINAE para excluir el serialize(_imp) funciones de resolución de sobrecarga, si la primera expresión dentro decltype no es válido (también conocido como, la función no existe).

los void() se utiliza para hacer el tipo de retorno de todas esas funciones void.

los 0 Se utiliza el argumento para preferir el os << obj sobrecarga si ambos están disponibles (literal 0 es de tipo int y como tal, la primera sobrecarga es una mejor combinación).


Ahora, probablemente desee un rasgo para verificar si existe una función. Afortunadamente, es fácil escribir eso. Tenga en cuenta, sin embargo, que necesita escribir un rasgo tú mismo para cada nombre de función diferente que desee.

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

Ejemplo vivo.

Y paso a las explicaciones. Primero, sfinae_true es un tipo de ayuda, y básicamente equivale a escribir decltype(void(std::declval<T>().stream(a0)), std::true_type{}). La ventaja es simplemente que es más corto.
A continuación, el struct has_stream : decltype(...) hereda de cualquiera std::true_type o std::false_type al final, dependiendo de si el decltype registrarse test_stream falla o no.
Último, std::declval le da un “valor” de cualquier tipo que pase, sin que necesite saber cómo puede construirlo. Tenga en cuenta que esto solo es posible dentro de un contexto no evaluado, como decltype, sizeof y otros.


Tenga en cuenta que decltype no es necesariamente necesario, ya que sizeof (y todos los contextos no evaluados) obtuvieron esa mejora. Es solo que decltype ya ofrece un tipo y, como tal, es más limpio. Aquí está un sizeof versión de una de las sobrecargas:

template<class T>
void serialize_imp(std::ostream& os, T const& obj, int,
    int(*)[sizeof((os << obj),0)] = 0)
{
  os << obj;
}

los int y long los parámetros siguen ahí por la misma razón. El puntero de matriz se utiliza para proporcionar un contexto donde sizeof puede ser usado.

  • La ventaja de decltype sobre sizeof es también que un temporal no se introduce mediante reglas especialmente diseñadas para llamadas a funciones (por lo que no es necesario tener derechos de acceso al destructor del tipo de retorno y no causará una instanciación implícita si el tipo de retorno es una instanciación de plantilla de clase ).

    – Johannes Schaub – litb

    11 de abril de 2014 a las 17:29


  • Microsoft aún no ha implementado Expression SFINAE en su compilador de C++. Solo imagino que podría ayudar a ahorrar tiempo a algunas personas, ya que estaba confundido por qué esto no funcionaba para mí. Sin embargo, es una buena solución, ¡no puedo esperar para usarla en Visual Studio!

    – Jonathan

    26 de enero de 2015 a las 6:22

  • Hay que decir, que static_assert(has_stream<X, char>() == true, "fail X"); compilará y no afirmará porque char se puede convertir a int, por lo que si no se desea ese comportamiento y desea que todos los tipos de argumentos coincidan, no sé cómo se puede lograr.

    – gabriel

    4 oct 2015 a las 14:10


  • Si está tan desconcertado como yo con los dos argumentos para decltype: decltype realmente solo toma uno; la coma es un operador aquí. Ver stackoverflow.com/questions/16044514/…

    – André

    17 de febrero de 2017 a las 6:06


  • Esto funciona perfectamente en situaciones que requieren tipos completos, pero en situaciones que no lo hacen, dará falsos negativos para tipos incompletos (declarados hacia adelante). agregué un sfinae_false contraparte y usó un tipo de retorno en el long anular lo detectado por la presencia de un destructor. Esto excluyó los tipos que aún estaban incompletos o no tenían destructores públicos. Excluir a los destructores no públicos era aceptable para mí.

    – Juan

    22 de mayo de 2017 a las 18:23

  • Me gusta como type_check se utiliza para garantizar que las firmas coincidan exactamente. ¿Hay alguna manera de hacer que coincida con cualquier método que pueda llamarse de la misma manera que un método con firma? Sign se podria llamar? (Ej. si Sign = std::string(T::*)()permitir std::string T::toString(int default = 42, ...) para hacer coincidir.)

    – j_random_hacker

    17 de noviembre de 2010 a las 1:37

  • Simplemente descubrí algo sobre esto que no era obvio de inmediato para mí, por lo que en caso de que ayude a otros: ¡chk no está y no necesita definirse! El operador sizeof determina el tamaño de la salida de chk sin necesidad de llamar a chk.

    – SCFrancés

    23 de enero de 2011 a las 17:24

  • @deek0146: Sí, T no debe ser un tipo primitivo, porque la declaración de puntero a método de T no está sujeta a SFINAE y generará un error para cualquier T que no sea de clase. En mi opinión, la solución más fácil es combinar con is_class comprobar desde impulso.

    – Jan Hudec

    22 de mayo de 2012 a las 12:04

  • ¿Cómo puedo hacer que esto funcione si mi toString es una función con plantilla?

    – franco

    20 de agosto de 2012 a las 2:00


  • ¿Es esto (o algo equivalente) en Boost?

    – Dan Nissenbaum

    29 de marzo de 2013 a las 6:24

¿Comprobacion con plantilla de la existencia de una funcion miembro
Morwenn

C++20 – requires expresiones

Con C ++ 20 vienen conceptos y herramientas variadas como requires expresiones que son una forma integrada de verificar la existencia de una función. Con ellos podrías reescribir tu optionalToString funcionar de la siguiente manera:

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

Pre-C++20 – Kit de herramientas de detección

N4502 propone un kit de herramientas de detección para su inclusión en la biblioteca estándar de C++17 que eventualmente se convirtió en la biblioteca fundamentals TS v2. Lo más probable es que nunca entre en el estándar porque ha sido subsumido por requires expresiones desde entonces, pero aún resuelve el problema de una manera un tanto elegante. El kit de herramientas presenta algunas metafunciones, que incluyen std::is_detected que se puede usar para escribir fácilmente metafunciones de detección de tipo o función en la parte superior. Así es como podría usarlo:

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

Tenga en cuenta que el ejemplo anterior no está probado. El kit de herramientas de detección aún no está disponible en las bibliotecas estándar, pero la propuesta contiene una implementación completa que puede copiar fácilmente si realmente la necesita. Funciona bien con la característica 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";
}

C++14 – Boost.Hana

Boost.Hana aparentemente se basa en este ejemplo específico y proporciona una solución para C++ 14 en su documentación, así que lo citaré directamente:

[…] Hana ofrece una is_valid función que se puede combinar con lambdas genéricas de C++14 para obtener una implementación mucho más limpia de lo mismo:

auto has_toString = hana::is_valid([](auto&& obj) -> decltype(obj.toString()) { });

Esto nos deja con un objeto de función. has_toString que devuelve si la expresión dada es válida en el argumento que le pasamos. El resultado se devuelve como un IntegralConstant, por lo que la constexpr-ness no es un problema aquí porque el resultado de la función se representa como un tipo de todos modos. Ahora, además de ser menos detallado (¡eso es una sola línea!), la intención es mucho más clara. Otros beneficios son el hecho de que has_toString se puede pasar a algoritmos de orden superior y también se puede definir en el ámbito de la función, por lo que no es necesario contaminar el ámbito del espacio de nombres con detalles de implementación.

Boost.TTI

Otro conjunto de herramientas un tanto idiomático para realizar dicha verificación, aunque menos elegante, es Boost.TTI, introducido en Boost 1.54.0. Para tu ejemplo, tendrías que usar la macro BOOST_TTI_HAS_MEMBER_FUNCTION. Así es como podría usarlo:

#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;

Entonces, podrías usar el bool para crear un cheque SFINAE.

Explicación

la macro BOOST_TTI_HAS_MEMBER_FUNCTION genera la metafunción has_member_function_toString que toma el tipo marcado como su primer parámetro de plantilla. El segundo parámetro de plantilla corresponde al tipo de retorno de la función miembro y los siguientes parámetros corresponden a los tipos de los parámetros de la función. El miembro value contiene true si la clase T tiene una función miembro std::string toString().

Alternativamente, has_member_function_toString puede tomar un puntero de función miembro como parámetro de plantilla. Por lo tanto, es posible reemplazar has_member_function_toString<T, std::string>::value por has_member_function_toString<std::string T::* ()>::value.

  • más conciso que 03

    – ZFY

    13 de marzo de 2020 a las 11:20

  • @ZFY Creo que Boost.TTI también funciona con C++ 03, pero es la solución menos elegante de todas.

    – Morwenn

    13 de marzo de 2020 a las 14:01

  • ¿Es realmente válida la solución C++20? Me gustaría, pero g ++ y msvc lo rechazan, solo lo acepta clang.

    – Bernd

    4 abr 2020 a las 20:17


  • en cppreference puede leer: Si una expresión requiere contiene tipos o expresiones no válidos en sus requisitos, y no aparece dentro de la declaración de una entidad con plantilla, entonces el programa está mal formado.

    – Bernd

    4 abr 2020 a las 20:36


  • @BerndBaumanns ¿En serio? Lo hice funcionar con el tronco GCC: godbolt.org/z/CBwZdE Tal vez tengas razón, solo verifiqué que funcionara pero no verifiqué si era legal de acuerdo con la redacción estándar.

    – Morwenn

    5 de abril de 2020 a las 11:51

Aunque esta pregunta tiene dos años, me atreveré a agregar mi respuesta. Con suerte, aclarará la solución anterior, indiscutiblemente excelente. Tomé las muy útiles respuestas de Nicola Bonelli y Johannes Schaub y las fusioné en una solución que es, en mi humilde opinión, más legible, clara y no requiere la typeof extensión:

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

Lo comprobé con gcc 4.1.2. El crédito es principalmente para Nicola Bonelli y Johannes Schaub, así que dales un voto si mi respuesta te ayuda 🙂

  • más conciso que 03

    – ZFY

    13 de marzo de 2020 a las 11:20

  • @ZFY Creo que Boost.TTI también funciona con C++ 03, pero es la solución menos elegante de todas.

    – Morwenn

    13 de marzo de 2020 a las 14:01

  • ¿Es realmente válida la solución C++20? Me gustaría, pero g ++ y msvc lo rechazan, solo lo acepta clang.

    – Bernd

    4 abr 2020 a las 20:17


  • en cppreference puede leer: Si una expresión requiere contiene tipos o expresiones no válidos en sus requisitos, y no aparece dentro de la declaración de una entidad con plantilla, entonces el programa está mal formado.

    – Bernd

    4 abr 2020 a las 20:36


  • @BerndBaumanns ¿En serio? Lo hice funcionar con el tronco GCC: godbolt.org/z/CBwZdE Tal vez tengas razón, solo verifiqué que funcionara pero no verifiqué si era legal de acuerdo con la redacción estándar.

    – Morwenn

    5 de abril de 2020 a las 11:51

Una solución simple para C++ 11:

template<class T>
auto optionalToString(T* obj)
 -> decltype(  obj->toString()  )
{
    return     obj->toString();
}
auto optionalToString(...) -> string
{
    return "toString not defined";
}

Actualización, 3 años después: (y esto no se ha probado). Para probar la existencia, creo que esto 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";
}

  • Esto es simple y elegante, pero estrictamente hablando no responde la pregunta de OP: no permite que la persona que llama controlar para la existencia de una función, siempre proveer eso. Pero agradable de todos modos.

    – Adrián W.

    13 de junio de 2018 a las 17:38

  • @AdrianW, buen punto. He actualizado mi respuesta. aunque no lo he probado

    – Aaron McDaid

    15 de junio de 2018 a las 18:49

  • En caso de que ayude a alguien más, no podría hacer que esto funcione sin template<typename> antes de la sobrecarga variádica: no se consideraba para su resolución.

    – aPonza

    21 de diciembre de 2018 a las 7:55

  • Nuevamente, esto no es válido para C++ 11.

    – Pedro

    11 de octubre de 2019 a las 7:05

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad