45

Suppose I have a template function:

template<typename T>
void f(T t)
{
    ...
}

and I want to write a specialization for all primitive integer types. What is the best way to do this?

What I mean is:

template<typename I where is_integral<I>::value is true>
void f(I i)
{
    ...
}

and the compiler selects the second version for integer types, and the first version for everything else?

cigien
  • 57,834
  • 11
  • 73
  • 112
Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319
  • Is there template specialization for functions in C++11? I think in C++03 it was overloading only. – Alexander Chertov Aug 22 '12 at 13:02
  • I've been looking for something similar, but failed. all I could do is just define a normal template, and inside I check if the given parameter is of integral type – Moataz Elmasry Aug 22 '12 at 13:04
  • 3
    @AlexanderChertov there is in C++03, but [it is complicated](http://www.gotw.ca/publications/mill17.htm). The same applies to C++11. – juanchopanza Aug 22 '12 at 13:04
  • 1
    @AlexanderChertov: Yes, function templates can be specialized even in C++03, but not partial specialization. – Ben Voigt Aug 22 '12 at 13:09

6 Answers6

65

Use SFINAE

// For all types except integral types:
template<typename T>
typename std::enable_if<!std::is_integral<T>::value>::type f(T t)
{
    // ...
}

// For integral types only:
template<typename T>
typename std::enable_if<std::is_integral<T>::value>::type f(T t)
{
    // ...
}

Note that you will have to include the full std::enable_if return value even for the declaration.

C++17 update:

// For all types except integral types:
template<typename T>
std::enable_if_t<!std::is_integral_v<T>> f(T t)
{
    // ...
}

// For integral types only:
template<typename T>
std::enable_if_t<std::is_integral_v<T>> f(T t)
{
    // ...
}
David
  • 27,652
  • 18
  • 89
  • 138
  • 4
    You have changed the return value of the function from void to something else. What if the original function returned a type? For example a class X? `template X f(T t)` ? How would it look then? – Andrew Tomazos Aug 22 '12 at 13:15
  • 1
    @AndrewTomazos-Fathomling, `std::ebable_if::type` is void, if given only 1 template parameter; or 2nd template parameter. – Lol4t0 Aug 22 '12 at 13:16
  • 3
    @AndrewTomazos-Fathomling The second template argument of `std::enable_if` is defaulted to `void`. That's your return type. I left it defaulted, but you can change it to whatever return type you want. You can even have integral types return a different type than non-integral types. – David Aug 22 '12 at 13:16
  • 3
    Oh so it would be `template typename enable_if::value, X>::type f(T t)` – Andrew Tomazos Aug 22 '12 at 13:18
  • @AndrewTomazos-Fathomling Yes – David Aug 22 '12 at 13:19
  • 2
    It is possible to make the use of `std::enable_if` [a bit more aesthetically pleasing](http://rmartinho.github.com/2012/06/01/almost-static-if.html), at least in my opinion. – Luc Danton Aug 23 '12 at 12:33
  • @LucDanton I like it. But I don't have template aliasing :( – David Aug 23 '12 at 12:56
  • And if you want to return something other than void, use [`enable_if`](http://en.cppreference.com/w/cpp/types/enable_if)'s `T=void` 2nd template parameter: `std::enable_if::value, size_t >::type` for example. – Peter Cordes Sep 21 '17 at 09:51
  • since C++14, you can replace `std::enable_if::value>::type` to `std::enable_if_t::value>` – Константин Гудков Apr 22 '18 at 21:44
  • not sure what is the point if you have to define multiple declaration in this case instead of only able to have 1 declaration and multiple specification. In the case of multiple declaration, makes more sense to just use overloading instead... unless in this case is 1 definition for all integral types.. agree.. – Raffaello Jun 09 '23 at 18:06
28

I would use overload resolution. That spares you from having to use the gross SFINAE hack. Unfortunately there are many areas where you can't avoid it, but this fortunately isn't one of those.

template<typename T>
void f(T t)
{
  f(t, std::is_integral<T>());
}

template<typename T>
void f(T t, std::true_type)
{ 
  // ...
}

template<typename T>
void f(T t, std::false_type)
{ 
  // ...
}
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
12

Using c++11, std::enable_if ( http://en.cppreference.com/w/cpp/types/enable_if ) can be used to do that:

template<typename T, class = typename std::enable_if<std::is_integral<T>::value>::type>
void f(T t) {...}
arnoo
  • 501
  • 2
  • 9
  • 2
    This still will not work, because you will get 2 overloads for integral type and compiler will not be able to select one. – Lol4t0 Aug 22 '12 at 13:12
  • 4
    What I don't like about this method is that you can actually pass a type to the second template argument and mess up the SFINAE (same problem with a default second parameter to the function). Doing it via return type is fool proof. On the other hand, to the uninitiated, it's not clear what the function returns. – David Aug 22 '12 at 13:24
9

Here is C++20 solution

template<std::integral T>
void f(T v) {
   ...
}

I think It's actually overloaded function but works the same.

LightSith
  • 795
  • 12
  • 27
  • this doesn't work as a specialization. if you define a generic template and then specializing an implementation only for integral this won't work. – Raffaello Jun 09 '23 at 17:36
7

You can use a helper template that you can specialize like this:

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

template <typename T, bool = std::is_integral<T>::value>
struct Foo {
        static void bar(const T& t) { std::cout << "generic: " << t << "\n"; }
};
template <typename T>
struct Foo<T, true> {
        static void bar(const T& t) { std::cout << "integral: " << t << "\n"; }
};

template <typename T>
static void bar(const T& t) {
        return Foo<T>::bar(t);
}

int main() {
        std::string s = "string";
        bar(s);
        int i = 42;
        bar(i);
        return 0;
}

output:

generic: string
integral: 42
mitchnull
  • 6,161
  • 2
  • 31
  • 23
  • 1
    +1 for the class template delegate, as this allows not only explicit but also partial specialization. This is also inline with the Sutter&Alexandrescu Coding Standards' Item 66: "don't specialize function templates." – TemplateRex Aug 22 '12 at 13:18
  • @rhalbersma I like this answer, but I don't think your reasoning is sound. No answer here used function template specialization. It simply isn't a practical option to use function template specialization for this problem. – David Aug 22 '12 at 13:51
  • The question itself asked for "specialization". – mitchnull Aug 22 '12 at 14:27
  • @Dave Even though your answer is technically correct, SFINAE is a much over-used and potentially dangerous tool. Both function template specialization and SFINAE rely on hard- to-debug function overload resolution effects. It is often easier to use tag dispatching (e.g. for more than one alternative, and for non-template functions) or delegate to class specialization (to also have a form of partial specialization). – TemplateRex Aug 22 '12 at 18:05
  • Yes I agree. I up voted this answer and I actually think it's superior to my own. But I have different reasoning. I like it because to the user of the function the signature looks correct (there's no crazy return type that they are unsure what the actual type is) – David Aug 22 '12 at 19:19
2

What about a more straightforward and better readable way by just implementing the different versions inside the function body?

    template<typename T>
    void DoSomething(T inVal) {
        static_assert(std::is_floating_point<T>::value || std::is_integral<T>::value, "Only defined for float or integral types");
        if constexpr(std::is_floating_point<T>::value) {
            // Do something with a float
        } else if constexpr(std::is_integral<T>::value) {
            // Do something with an integral
        }
    }

You dont have to worry about performance. The conditions are compile time constant and a descent compiler will optimize them away. "if constexpr" is c++17 unfortunately but you may remove "constexpr" when both versions compile without errors for both types

Ole Dittmann
  • 1,764
  • 1
  • 14
  • 22
  • This either requires: both `// Do something with a float` compiles for non-float T and `// Do something with an integral` compiles for non-integral T; or `if constexpr`, which didn't exist in 2012 when the question was asked – Caleth Jul 05 '18 at 13:07
  • you mean back in 2012 the compiler would have always tried to compile both versions for any type even if it could have discarded the wrong version already at compile time? – Ole Dittmann Jul 06 '18 at 11:14
  • "both versions"? Yes, you have an if statement. Both branches must be legal C++ statements. This is *exactly what* `if constexpr` was created for – Caleth Jul 06 '18 at 11:17
  • Question is about [tag:c++11], which doesn't have `if constexpr`. – Toby Speight Mar 13 '19 at 12:29