0

I am trying to understand how does SFINAE work in C++98.
My aim is to write a simple template function that would only be called when an integer is passed. But the plan is to do it without specializing a function for typename T = int, but definining a template function with a dummy parameter that checks wether the pased element is an integer. Heres my toy code :

// header.h

#include <iostream>
#include <cstddef>

struct false_type {
    static const bool value = false;
};

struct true_type {
    static const bool value = true;
};

template < bool, typename T >
struct enable_if
{};

template <typename T>
struct enable_if<true, T>
{ typedef T type; };

template <typename T>
struct is_integral : false_type {};

template <>
struct is_integral<int> : true_type {};

/* ... more is_integral specializations ... */

template < typename T > 
void print(T& value, typename enable_if<!is_integral<T>::value,T>::type* = 0)
{
    std::cout << "FIRST " << value << std::endl;
}

template < typename T > 
void print(T& value)
{
    std::cout << "SECOND " << value << std::endl;
    std::cout << std::boolalpha;
    std::cout << is_integral<T>::value << std::endl;
}

// main.cpp

#include "header.hpp"

int main() {
    int a = 123;
    print<int>(a);
}

It is compiled as follows : g++ -Wall -Wextra -Werror -std=c++98 -pedantic -g3 -Wno-c++0x-compat main.cpp .
I took the enable_if syntax from this question's first answer, which needed for an implementation of enable_if and is_integral to be carried out in C++98 (<type_traits> is not a C++98 header).
My problem is that this program outputs:

SECOND 123
true

My question is, why does this happen? I expected the first implementation of print to be called. Is my approach impossible (i.e. this can only be done by specializing a generic print function to int), or am I doing something wrong ?

carce-bo
  • 480
  • 2
  • 10
  • 1
    `enable_if<!is_integral` - The condition is inverted compared to what you describe as wanting to happen. – StoryTeller - Unslander Monica Oct 20 '22 at 10:48
  • If it helps, wiritng `typename enable_if::value,T>::type` as the second argument also calls the second print implementation. The code shown in the question tries to say "if there exists no pointer type to enable_if::type" which is the same as saying if cond is false, which for integers, it should be. – carce-bo Oct 20 '22 at 10:53
  • There may be a good reason for that as well, but the thing to bear in mind is that SFINAE is about *pruning* overloads. Overload resolution will still choose a best match from the unpruned ones. An immediate fix is to have both overloads constrained with inverted conditions. – StoryTeller - Unslander Monica Oct 20 '22 at 10:57
  • "if there exists no pointer type to enable_if::type" - then the overload is **pruned**. You want it *not pruned*. – StoryTeller - Unslander Monica Oct 20 '22 at 10:59
  • @carce-bo If you remove the `!`, the program will not compile. You'll get a error saying ambiguity error. [Demo](https://onlinegdb.com/RWT7fH-DA7) – Jason Oct 20 '22 at 11:05
  • I think I get it now. the `typename ... = 0` stands for "this argument is a dummy one" but not "this argument does not exist (i.e. type not defined) ". I was understanding it in the wrong way. – carce-bo Oct 20 '22 at 11:06
  • @Jason Liam yes I've been trying out some things given StoryTeller's comments. – carce-bo Oct 20 '22 at 11:06
  • Do you know of any documentation that mentions the `typename = 0` syntax ? – carce-bo Oct 20 '22 at 11:07
  • @carce-bo You want that the function template should only be called when `T` is int right? Should i provide an answer? – Jason Oct 20 '22 at 11:09
  • @Jason Liam I do have now a compiling answer, but go ahead so I can see if we're on the same page. – carce-bo Oct 20 '22 at 11:09
  • 1
    @carce-bo See [this demo](https://onlinegdb.com/lZQ16XU9X), this will work only when `T` is int. You don't need any other overload. – Jason Oct 20 '22 at 11:12
  • Ok it is the same I have only difference is you wrote type* instead of type, which is trivial since one implies the other. – carce-bo Oct 20 '22 at 11:13

1 Answers1

0

My aim is to write a simple template function that would only be called when an integer is passed.

There is no need to provide 2 overloads. You can just remove the ! and have the first primary template as shown below:

template < typename T > 
void print(T& value, typename enable_if<is_integral<T>::value,T>::type* = 0)
{
    std::cout << "FIRST " << value << std::endl;
}

int main() {
    int a = 123;
    print<int>(a);  //works 
    //print<double>(4.4);                      //doesn't work
    //print<std::string>(std::string("f"));   //doesn't work
    //print(3.3);                             //doesn't work
    double d = 3.3;
    //print(d);                               //doesn't work
}
Jason
  • 36,170
  • 5
  • 26
  • 60