15

I'm trying to return an int64_t if std::is_integral<>::value is true.

Otherwise, I would like to call to_int64t() on the object.

My attempt below is failing because partial specialisation of function templates are not allowed.

CODE

#include <type_traits>
#include <cstdint>

template<class T,bool is_integral_type>
int64_t to_int64t( const T& t )
{
        return t;
}

template<class T>
int64_t to_int64t<T,std::is_integral<T>::value>( const T& t )
{
        return t;
}

template<class T>
int64_t to_int64t<T,!std::is_integral<T>::value>( const T& t )
{
        return t.to_int64t();
}

int main()
{
        int64_t i = 64;
        auto x = to_int64t( i );
}
kfmfe04
  • 14,936
  • 14
  • 74
  • 140
  • possible duplicate: http://stackoverflow.com/questions/12073689/c11-template-function-specialization-for-integer-types – legends2k Oct 03 '13 at 11:53

2 Answers2

32

Function templates cannot be partially specialized and, in general, it is not a good idea to use function template specialization.

One way to achieve what you want is to use a technique called tag dispatching, which basically consists in providing a forwarder function that selects the right overload based on the value of an extra dummy argument:

#include <type_traits>
#include <cstdint>

template<class T>
int64_t to_int64t( const T& t, std::true_type )
{
    return t;
}

template<class T>
int64_t to_int64t( const T& t, std::false_type )
{
    return t.to_int64t();
}

template<class T>
int64_t to_int64t( const T& t )
{
    return to_int64t(t, std::is_integral<T>());
}

int main()
{
    int64_t i = 64;
    auto x = to_int64t( i );
}

Another possibility is to use the classical SFINAE technique based on std::enable_if. This is how that could look like (notice that, since C++11, default template arguments on function templates are allowed):

#include <type_traits>
#include <cstdint>

template<class T, typename std::enable_if<
    std::is_integral<T>::value>::type* = nullptr>
int64_t to_int64t( const T& t )
{
    return t;
}

template<class T, typename std::enable_if<
    !std::is_integral<T>::value>::type* = nullptr>
int64_t to_int64t( const T& t )
{
    return t.to_int64t();
}

int main()
{
    int64_t i = 64;
    auto x = to_int64t( i );
}

Yet another possibility, although more verbose, is to define helper class templates (which can be partially specialized) in a detail namespace and provide a global forwarder - I would not use this technique for this use case, but I am showing it because it might come handy in related design situations:

#include <type_traits>
#include <cstdint>

namespace detail
{
    template<class T, bool = std::is_integral<T>::value>
    struct helper { };

    template<class T>
    struct helper<T, true>
    {
        static int64_t to_int64t( const T& t )
        {
            return t;
        }
    };

    template<class T>
    struct helper<T, false>
    {
        static int64_t to_int64t( const T& t )
        {
            return t.to_int64t();
        }
    };
}

template<class T>
int64_t to_int64t( const T& t )
{
    return detail::helper<T>::to_int64t(t);
}

int main()
{
    int64_t i = 64;
    auto x = to_int64t( i );
}
Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • Oh my, this is beautiful. :) – David G Mar 24 '13 at 13:27
  • Does the second one compile for you ? I get error C4519: default template arguments are only allowed on a class template – Alon Mar 24 '13 at 13:45
  • 3
    @Alon: Default template arguments on function templates where introduced with C++11, so you should compile with the `std=c++11` option (you may have to upgrade your compiler to the latest version if this is not supported) – Andy Prowl Mar 24 '13 at 13:46
  • Thanks, I suppose that is only available in gcc (Im using VS) – Alon Mar 24 '13 at 13:47
  • @Alon: OK, if you are using VS then you would need VS2012. Anyway you could make that default template argument a default *function* argument, which is less elegant but would still work – Andy Prowl Mar 24 '13 at 13:48
  • @AndyProwl: I am using 2012 and it still does not compile; what do you mean? move it from template signature to function signature second parameter? then the user has to type it ? – Alon Mar 24 '13 at 13:50
  • @Alon: Hm, then it looks like VS2012 support is still pretty limited. Yes, that's what I meant, and no, the user shall *not* type it, because it has a default value (`= nullptr`). The problem with this version is that the user *could* type it, which is not desirable. As long as the user will only pass one argument, however, this will work as intended. – Andy Prowl Mar 24 '13 at 13:51
  • @AndyProwl: Also, any chance you show an example of how to put it in the function arguments? – Alon Mar 24 '13 at 13:52
  • Thanks, +1 for patience :) – Alon Mar 24 '13 at 13:54
  • @AndyProwl: could you explain the compiler? if the enable_if failes it just doesn't select this function? – Alon Mar 24 '13 at 13:57
  • 1
    @Alon: Yes, that's called SFINAE (Substitution Failure Is Not An Error): the compiler tries to instantiate the function template's signature, and if it fails, it just discards the corresponding function from the overload set and considers only the other viable functions. – Andy Prowl Mar 24 '13 at 13:58
  • @AndyProwl: Both 1 and 3 are tag dispatching. 1 being tag dispatching by instance, while 3 being tag dispatching by type. ~[Source](http://barendgehrels.blogspot.com/2010/10/tag-dispatching-by-type-tag-dispatching.html) – legends2k Oct 03 '13 at 12:07
8

You can just use std::enable_if:

template<class T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
int64_t to_int64t( const T& t )
{
        return t;
}

template<class T, typename std::enable_if<!std::is_integral<T>::value, int>::type = 0>
int64_t to_int64t( const T& t )
{
        return t.to_int64t();
}
Stephan Dollberg
  • 32,985
  • 16
  • 81
  • 107