1

I want to write template function that should just be called when the type is integer, for example int8, int16, int32, int64, uint8, uint16, uint32, uint64; and an other template function (same name) with different code just for FLOAT type float32, float64... There is anyway to do it using templates functions in C++ ? For example :

template <class T>
void fun(T b)
{
   /// Code for integer type
}


 template <class S>
void fun(S a)
{
   /// Code for floating type
}
Justin
  • 24,288
  • 12
  • 92
  • 142
Aniss Be
  • 163
  • 8
  • Related, but there are techniques that read better: https://stackoverflow.com/q/19985160/1896169 – Justin Jan 24 '18 at 23:24
  • just use overloads - thats what they are for – pm100 Jan 24 '18 at 23:24
  • 2
    @pm100 I wouldn't recommend that someone write all the overloads for all the integer and 2 float types. That's a lot of unnecessary code duplication – Justin Jan 24 '18 at 23:25
  • Experience (and time pressure) have taught me to choose a single type and stick with it. Of course if you're a library writer things may be different. – Robinson Jan 24 '18 at 23:26
  • 4
    Use SFINAE. One using `std::is_intergal` and `!is_floating_point` and the other using the reverse – NathanOliver Jan 24 '18 at 23:27
  • You could use explicit template instantiation to only insatiate for a few specific types. https://stackoverflow.com/a/2351622/14065 – Martin York Jan 24 '18 at 23:29
  • @pm100 Kind of proves my point that I forgot about `long double`... – Justin Jan 24 '18 at 23:32
  • @NathanOliver: why check for `!is_floating_point`? `std::is_integral` does not include floating point types. I think you meant to say "one using `std::is_integral` and the other using `std::is_floating_point`"... – Remy Lebeau Jan 25 '18 at 00:07
  • @Justin - his original title said he wanted different code for each one. Classic overload case. Now title is changed – pm100 Jan 25 '18 at 00:14
  • @pm100 That's a valid point. Thanks for pointing that out to me. But I do believe that the title change is appropriate, as it better matches what's in the body of the question – Justin Jan 25 '18 at 00:16
  • @RemyLebeau Yep. misspoke there. They just need to use one, – NathanOliver Jan 25 '18 at 00:33

3 Answers3

5

Yes, this is possible. One way of doing this is to use SFINAE:

template <class T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
void fun(T b)
{
   /// Code for integer type
}


template <class S, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
void fun(S a)
{
   /// Code for floating type
}

Note that the 2 conditions for the enable_ifs have to be disjoint, which we can see from the documentation of std::is_integral and std::is_floating_point that they are. If they weren't, the conditions would have to look like std::is_integral_v<T> && !std::is_floating_point_v<T>.

What is happening here is that the std::enable_if_t makes one of the overloads "fail to compile" if the condition is not satisfied, which means that the overload is simply not considered due to SFINAE.


If you are using C++17, you might want to consider using if constexpr instead:

template <class T>
void fun(T b)
{
    if constexpr (std::is_integral_v<T>) {
        /// Code for integer type
    } else if constexpr (std::is_floating_point_v<T>) {
        /// Code for floating type
    }
}
Justin
  • 24,288
  • 12
  • 92
  • 142
3

Depending on what your full intent is, outside of SFINAE and std::enable_if, you can use regular function overloads and/or template specialization too:

#include <iostream>
#include <cstdint>

template < typename T >
void fun(T b)
{
    std::cout << "Generic called: " << b << std::endl;
}

void fun(int16_t b)
{
    std::cout << "int16_t: " << b << std::endl;
}

void fun(int32_t b)
{
    std::cout << "int32_t: " << b << std::endl;
}

void fun(float b)
{
    std::cout << "float: " << b << std::endl;
}

template <>
void fun<int64_t>(int64_t b)
{
    std::cout << "int64_t specialization: " << b << std::endl;
}

int main(int argc, char** argv)
{
    double dbl = 3.14159;
    float flt = 1.12345;
    int16_t i16 = 16;
    int32_t i32 = 32;
    fun(dbl);
    fun(flt);
    fun(i16);
    fun(i32);
    fun(5.555f);
    fun(32);
    fun(std::numeric_limits<int64_t>::max());
    return 0;
}

Hope that can help.

txtechhelp
  • 6,625
  • 1
  • 30
  • 39
  • For cases where you want to overload all of `int8_t` through `int64_t` and `uint8_t` through `uint64_t`, this solution has a lot of code duplication. However, when I've been in such a case, I usually find that it's unnecessary, and I could instead just overload for `int64_t` and `uint64_t` and call it done. – Justin Jan 25 '18 at 00:01
  • I completely agree, @Justin !! OP's question hints that they want individual functions for each type though, which is why suggested keeping simple function overloads if that was indeed their intent. – txtechhelp Jan 25 '18 at 00:03
0

According to SFINAE, we can design compilation errors for one type while letting the other go in the template substitution phase. In the case of integral type vs. floating-point type, we can make use of the bit-related operators for integers like ~. For example,

#include <iostream>

template <typename T, T=~0>
void f_(T a, int)  // additional tags for unambiguous overloading, same below
{
    std::cout << a << " is of integral type." << std::endl;
}

template <typename T>
void f_(T a, char)
{
    std::cout << a << " is of floating-point type." << std::endl;
}

template <typename T>
void f(T a)
{
    f_<T>(a, 0);
}


int main()
{
    f<char>('a');
    f<int>(0);
    f<float>(1.5f);
    f<double>(2.5);
    return 0;
}

gives

a is of integral type.
0 is of integral type.
1.5 is of floating-point type.
2.5 is of floating-point type.
Ziyuan
  • 4,215
  • 6
  • 48
  • 77