0

Here is an MCVE of what I'm trying to achieve:

#include <limits>
#include <iostream>

// enable_if (I'm stuck with a c++98 compiler)
template<bool B, class T = void> struct enable_if {};
template<class T>                struct enable_if<true, T> { typedef T type; };

// sfinae
template<typename T> const char*
f(typename enable_if<std::numeric_limits<T>::is_integer, T>::type t) { return "sfinae"; }
template<typename T> const char*
f(T t) { return ""; }

// test
int main()
{
    std::cout << f(3) << "\n"; // returns an empty string
    std::cout << f(3.0) << "\n"; // returns an empty string
}

(try it on coliru)

I was expecting the call to f(3) to return "sfinae". What am I doing wrong?

For the first version to be called, I have to manually call f<int>(3). I'm puzzled.

YSC
  • 38,212
  • 9
  • 96
  • 149

1 Answers1

4

The following will work:

template <typename T>
const char* f_impl(
    typename enable_if<std::numeric_limits<T>::is_integer, T>::type t) {
  return "sfinae";
}

template <typename T>
const char* f_impl(
    typename enable_if<!std::numeric_limits<T>::is_integer, T>::type t) {
  return "";
}

template <typename T>
const char* f(T t) {
  return f_impl<T>(t);
}

on coliru


Your original code will work if you invoke f as follows:

std::cout << f<int>(3) << "\n";
std::cout << f<double>(3.0) << "\n";

This happens because the first f (the SFINAE one) will never be able to deduce T, as it's inside a non-deduced context. This means that it will never be called unless you explicitly specify the template argument!

By adding a layer of indirection that deduced T and then explicitly calls f_impl, you can easily have both deduction and SFINAE.


In C++11, you don't even need SFINAE:

const char* f_impl(std::true_type)  { return "sfinae"; }
const char* f_impl(std::false_type) { return ""; }

template<typename T> const char* f(T t) 
{ 
    return f_impl(std::is_integral<T>{}); 
}
Community
  • 1
  • 1
Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416