How to switch passed value through C++ standard type trait?

897 Views Asked by At

I want to switch passed value through C++ standard type trait. Here is the test code show what I mean:

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

It doesn't work of course. I try my best to test and find a way to implement this:

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

It works, but really ugly code. Is there any kind of smart way to solve this?

3

There are 3 best solutions below

5
On BEST ANSWER

You can use SFINAE to reject overloads

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

or use tag dispatching similar to what you've shown, but handle the alternatives differently to reduce the number of cases

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>());
}
2
On

You can achieve this with SFINAE like so

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

There are a couple of things to understand here, the first is SFINAE and how that works, but answering that will make this answer way too long and I will probably do a worse job than others, so here is a reference to std::enable_if (http://en.cppreference.com/w/cpp/types/enable_if) and here is a reference to SFINAE (http://eli.thegreenplace.net/2014/sfinae-and-enable_if/)

The other important thing to note here is the std::decay_t in one of the function templates. If the passed in type is a const string& then the code will not be correct anymore as std::is_same<const string&, std::string>::value is false. The std::decay_t removes all references, const qualifiers and volatile qualifiers. See http://en.cppreference.com/w/cpp/types/decay

The WhichType template is a technique used to inspect the type of a variable as described by Scott Meyers in the book Effective Modern C++. Most compilers will output an error showing the type of the thing in the template. Therefore it's useful when you want to know the type of an expression. In this case it will output the return type of the function allowing you to see which function was called.

1
On

Just for the record, in C++17 you can use 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;
}

This code compiles in Clang 3.9 and GCC 7.