Antoine Morrier
Antoine Morrier

Reputation: 4078

SFINAE with alias, overloading issue

I have this code :

#include <iostream>
#include <type_traits>

template <typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
using FloatingPoint = T;

template <typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
using Integral = T;

template <typename T> void f(Integral<T>) { std::cout << "integral" << std::endl; }

template <typename T> void f(FloatingPoint<T>) {
  std::cout << "floating point" << std::endl;
}

int main() {
  f(5);
  return 0;
}

This code does not compile :

prog.cc:12:28: error: redefinition of 'f'
template <typename T> void f(FloatingPoint<T>) {
                           ^
prog.cc:10:28: note: previous definition is here
template <typename T> void f(Integral<T>) { std::cout << "integral" << std::endl; }
                           ^
prog.cc:17:3: error: no matching function for call to 'f'
  f(5);
  ^
prog.cc:12:28: note: candidate template ignored: substitution failure [with T = int]
template <typename T> void f(FloatingPoint<T>)

It is kind of weird because it understands that there is a substitution failure, but he does not want to take the first overloading.

So I know two ways to handle with this. The first thing is to put the enable_if either as an argument, or as a return type. Is there other way to handle this in a "pretty" way? Or have I to use one of the two solutions I wrote before?

The goal is to write clean code. I'd love to use concept from C++20 but it seems that it is not possible everywhere yet (I mean GCC, Clang, and MSVC) https://en.cppreference.com/w/cpp/compiler_support. Or this website is not up to date and both MSVC (2019), GCC 10 and clang 9 supports well concept?

Upvotes: 2

Views: 84

Answers (1)

I think your problem is that FloatingPoint and Integral directly depend on T. Re-organizing your code you can get the following. You can run the code here: https://onlinegdb.com/By-T4bBfB .

#include <iostream>
#include <type_traits>

template<typename T>
using isFloatingPoint = std::enable_if_t<std::is_floating_point_v<T>, bool>;

template<typename T>
using isIntegral = std::enable_if_t<std::is_integral_v<T>, bool>;


template <typename T, isIntegral<T> = true>
void f(T) { 
    std::cout << "integral" << std::endl;
}

template <typename T, isFloatingPoint<T> = true>
void f(T) { 
    std::cout << "floatingPoint" << std::endl;

}

int main() {
  f(5);
  f(5.0);
}

Alternatively, given that you are using C++17 you can use if constexpr. You say that is not scalable in the comments. In my opinion it is, but most likely I do not understand the constraints you are operating under. For this see https://godbolt.org/z/gLc7YE . if constexpr allows you to have only one function and even different return types pretty easily.

#include <type_traits>
#include <string>

template<typename T>
auto f(T){
    if constexpr (std::is_floating_point_v<T>){
        return 0.0f;
    } 
    else if constexpr (std::is_integral_v<T>){
        return 0;
    } else {
        return 0.0;
    }
}

int main() {
  static_assert(std::is_same_v<decltype(f(5)), int>, "ERROR 1");
  static_assert(std::is_same_v<decltype(f(5.0)), float>, "ERROR 2");
  static_assert(std::is_same_v<decltype(f(std::string{"JJ"})), double>, "ERROR 3");
  return 0;
}

Finally I would also to note that having a different return type for the two f() in your code would solve your issue (run here the code: https://onlinegdb.com/By-9HZrMS):

#include <iostream>
#include <type_traits>

template <typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
using FloatingPoint = T;

template <typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
using Integral = T;

template <typename T> int f(Integral<T>) { std::cout << "integral" << std::endl;  return 0;}

template <typename T> float f(FloatingPoint<T>) {
  std::cout << "floating point" << std::endl;
  return 0.0f;
}

int main() {
  f(5);
  f(5.0);
  return 0;
}

Upvotes: 1

Related Questions