Quiero cambiar el valor pasado a través del rasgo de tipo estándar de C ++. Aquí está el código de prueba que muestra lo que quiero decir:

template<typename T>
T _func(T t, std::is_integral<T>||std::is_enum<T>)
{
    return t;
}

template<typename T>
T _func(T t, std::is_floating_point<T>)
{
    return t;
}

template<typename T>
T _func(T t, std::is_same<T, std::string>)
{
    return t;
}

template<typename T>
T func(T t)
{
    return _func(t, T)
}

No funciona, por supuesto. Hago mi mejor esfuerzo para probar y encontrar una manera de implementar esto:

int testint(int t){ return t;}
std::string testString(std::string t ){return t;}
float testreal(float t ){return t;}

template <typename T>
T test_string_type(T t, std::true_type) {
    return testString(t);
}

template <typename T>
T test_string_type(T t, std::false_type) {
    return t;
}

template<typename T>
T test_real_type(T t, std::true_type)
{
    return testreal(t) ;
}

template<typename T>
T test_real_type(T t, std::false_type)
{
    return test_string_type(t, std::is_same<T, std::string>());
}

template<typename T>
T test_enum_type(T t, std::true_type)
{
    return testint(t) ;
}

template<typename T>
T test_enum_type(T t, std::false_type)
{
    return test_real_type(t, std::is_floating_point<T>());
}

template<typename T>
T test_integer_type(T t, std::true_type)
{
    return testint(t) ;
}

template<typename T>
T test_integer_type(T t, std::false_type)
{
    return test_enum_type(t, std::is_enum<T>()) ;
}

template<typename T>
T test(T t)
{
    return test_integer_type(t, std::is_integral<T>());
}

Funciona, pero el código realmente feo. ¿Hay algún tipo de forma inteligente de resolver esto?

1
Ringo_D 29 dic. 2016 a las 06:51

3 respuestas

La mejor respuesta

Puede usar SFINAE para rechazar sobrecargas

template<typename T>
typename std::enable_if<std::is_integral<T>::value || std::is_enum<T>::value>::type
 func(T t)
{
    return t;
}

template<typename T>
typename std::enable_if<std::is_floating_point<T>::value>::type
 func(T t)
{
    return t;
}

template<typename T>
typename std::enable_if<std::is_same<T, std::string>::value>::type 
 func(T t)
{
    return t;
}

O use el despacho de etiquetas similar a lo que ha mostrado, pero maneje las alternativas de manera diferente para reducir el número de casos

namespace detail
{
template<typename T>
T func(T t, std::false_type /*is_string*/, std::false_type /*is_float*/, std::true_type /*is_int*/)
{
    return t;
}

template<typename T>
T func(T t, std::false_type /*is_string*/, std::true_type /*is_float*/, std::false_type /*is_int*/)
{
    return t;
}

template<typename T>
T func(T t, std::true_type /*is_string*/, std::false_type /*is_float*/, std::false_type /*is_int*/)
{
    return t;
}
}

template<typename T>
T func(T t)
{
    return detail::func(t, std::is_same<string, T>(),
                           std::is_floating_point<T>(),
                           std::integral_constant<bool, std::is_integral<T>::value || std::is_enum<T>::value>());
}
3
Praetorian 29 sep. 2017 a las 19:50

Solo para el registro, en C ++ 17 puede usar if constexpr:

template <typename T>
T func(T t) {
    if constexpr (std::is_integral<T>::value || std::is_enum<T>::value) {
        return t + 1;
    } else if constexpr (std::is_floating_point<T>::value) {
        return t + 2.5;
    } else if constexpr (std::is_same<T, std::string>::value) {
        return t + " suffix";
    }

    return t;
}

Este código se compila en Clang 3.9 y GCC 7.

3
Anton Savin 29 dic. 2016 a las 04:14

Puedes lograr esto con SFINAE así

#include <iostream>
#include <type_traits>
#include <string>

using namespace std;

struct One {};
struct Two {};
struct Three {};

template <typename T,
          typename std::enable_if_t<std::is_integral<T>::value
                || std::is_enum<T>::value>* = nullptr>
One _func(T)
{
    return One{};
}

template<typename T,
         typename std::enable_if_t<std::is_floating_point<T>::value>* = nullptr>
Two _func(T)
{
    return Two{};
}

template<typename T,
         typename std::enable_if_t<std::is_same<
            std::decay_t<T>, std::string>::value>* = nullptr>
Three _func(T)
{
    return Three{};
}

template<typename T>
auto func(T t)
{
    return _func(t);
}

template <typename...>
struct WhichType;

int main() {
    WhichType<decltype(func(1))>{};
    WhichType<decltype(func(1.0))>{};
    WhichType<decltype(func(std::string{}))>{};
    return 0;
}

Hay un par de cosas para entender aquí, la primera es SFINAE y cómo funciona, pero responder eso hará que esta respuesta sea demasiado larga y probablemente haré un trabajo peor que otros, así que aquí hay una referencia a {{X0} } (http://en.cppreference.com/w/cpp/types/enable_if) y aquí hay una referencia a SFINAE (http: //eli.thegreenplace .net / 2014 / sfinae-and-enable_if /)

La otra cosa importante a tener en cuenta aquí es el std::decay_t en una de las plantillas de función. Si el tipo pasado es un const string&, el código ya no será correcto ya que std::is_same<const string&, std::string>::value es falso. El std::decay_t elimina todas las referencias, calificadores constantes y calificadores volátiles. Consulte http://en.cppreference.com/w/cpp/types/decay

La plantilla WhichType es una técnica utilizada para inspeccionar el tipo de una variable como lo describe Scott Meyers en el libro Effective Modern C ++. La mayoría de los compiladores mostrarán un error que muestra el tipo de elemento en la plantilla. Por lo tanto, es útil cuando quieres saber el tipo de una expresión. En este caso, generará el tipo de retorno de la función que le permite ver qué función se llamó.

1
Curious 29 dic. 2016 a las 07:51