How do I use std::enable_if with a self-deducing r

2020-05-20 08:42发布

C++14 will have functions whose return type can be deduced based on the return value.

auto function(){
    return "hello world";
}

Can I apply this behaviour to functions that use enable_if for the SFINAE by return type idiom?

For example, let's consider the following two functons:

#include <type_traits>
#include <iostream>

//This function is chosen when an integral type is passed in
template<class T >
auto function(T t) -> typename std::enable_if<std::is_integral<T>::value>::type {
    std::cout << "integral" << std::endl;
    return;
}

//This function is chosen when a floating point type is passed in
template<class T >
auto function(T t) -> typename std::enable_if<std::is_floating_point<T>::value>::type{
    std::cout << "floating" << std::endl;
    return;
}

int main(){

  function(1);    //prints "integral"
  function(3.14); //prints "floating"

}

As you can see, the correct function is chosen using the SFINAE by return type idiom. However, these are both void functions. The second parameter of enable_if is default set to void. This would be the same:

//This function is chosen when an integral type is passed in
template<class T >
auto function(T t) -> typename std::enable_if<std::is_integral<T>::value, void>::type {
    std::cout << "integral" << std::endl;
    return;
}

//This function is chosen when a floating point type is passed in
template<class T >
auto function(T t) -> typename std::enable_if<std::is_floating_point<T>::value, void>::type{
    std::cout << "floating" << std::endl;
    return;
}

Is there something I can do to these two functions, so that their return type is deduced by the return value?

gcc 4.8.2 (using --std=c++1y)

4条回答
做个烂人
2楼-- · 2020-05-20 08:57

std::enable_if doesn't have to be in the return type, as of C++11 it can be part of the template parameters.

So your equivalent functions can be (or, well, something to this effect):

enum class enabler_t {};

template<typename T>
using EnableIf = typename std::enable_if<T::value, enabler_t>::type;

//This function is chosen when an integral type is passed in
template<class T, EnableIf<std::is_integral<T>>...>
auto function(T t) {
    std::cout << "integral" << std::endl;
    return;
}

//This function is chosen when a floating point type is passed in
template<class T, EnableIf<std::is_floating_point<T>>...>
auto function(T t) {
    std::cout << "floating" << std::endl;
    return;
}

It can also be a parameter in the function:

//This function is chosen when an integral type is passed in
template<class T>
auto function(T t, EnableIf<std::is_integral<T>>* = nullptr) {
    std::cout << "integral" << std::endl;
    return;
}

//This function is chosen when a floating point type is passed in
template<class T>
auto function(T t, EnableIf<std::is_floating_point<T>>* = nullptr) {
    std::cout << "floating" << std::endl;
    return;
}

This will keep the automatic type deduction and SFINAE.

查看更多
家丑人穷心不美
3楼-- · 2020-05-20 08:57

As mentioned elsewhere std::enable_if can be used to form either a return type; a function parameter; or a template parameter.

However, the latter two methods have the disdvantage that they change the signature of the relevant function or object. Using std::enable_if at the return type, on the other hand, leaves the function and template parameter count unchanged.

With the automatic inference of lambda return types in C++11 (likely extended to normal functions in C++14) it would be ideal if there was a technique to both have the return type inferred; and use std::enable_if on the return type. To have your cake an eat it - almost. Alas it seems that this is currently impossible.

查看更多
forever°为你锁心
4楼-- · 2020-05-20 09:00

std::enable_if can be a return type, a function parameter, or a template parameter. You will get a function redefinition error if you use return type or template parameter, so you need to use std::enable_if as a function parameter:

#include <type_traits>
#include <iostream>

template<class T, typename = typename std::enable_if<std::is_integral<T>::value, void>::type>
auto function(T t, typename std::enable_if<std::is_integral<T>::value, void>::type* dummy = nullptr) {
    std::cout << "integral" << std::endl;
    return 0;
}

//This function is chosen when a floating point type is passed in
template<class T, typename = typename std::enable_if<std::is_floating_point<T>::value, void>::type>
auto function(T t, typename std::enable_if<std::is_floating_point<T>::value, void>::type* dummy = nullptr) {
    std::cout << "floating" << std::endl;
    return 0.0f;
}

int main() 
{
    auto ret = function(0); // integral
    auto ret2 = function(0.0f); // floating
    std::cout << std::boolalpha << std::is_integral<decltype(ret)>::value << std::endl; // true
    std::cout << std::is_floating_point<decltype(ret2)>::value << std::endl; // true
}
查看更多
放荡不羁爱自由
5楼-- · 2020-05-20 09:08

In the line of the answer by @user1508519, we can remove the enable_if parameter from the method and keep it only as a template parameter. We rely on the fact that enable_if<false> doesn't define type, so enable_if<false>::type, which doesn't exist, is a good tool for SFINAE -- in the method signature, which includes the template parameters.

There is no need to actually use this template parameter in the method itself!

Thus:

template<class T,
typename std::enable_if<std::is_integral<T>::value>::type* dummy = nullptr>
auto function(T t) {
    std::cout << "integral" << std::endl;
    return 0;
}

// This function is chosen when a floating point type is passed in
template<class T, 
typename std::enable_if<std::is_floating_point<T>::value>::type* dummy = nullptr>
auto function(T t) {
    std::cout << "floating" << std::endl;
    return 0.0f;
}

int main() {
    auto ret = function(0); // integral
    auto ret2 = function(0.0f); // floating
    cout << std::boolalpha;
    cout << std::is_integral<decltype(ret)>::value << endl; // true
    cout << std::is_floating_point<decltype(ret2)>::value << endl; // true
}

Output

integral
floating
true
true
查看更多
登录 后发表回答