Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

enable_if to Add a function parameter that has a default argument?

I can't understand the second scenario presented here. It says:

•Scenario 2: Adding a function parameter that has a default argument:

template <your_stuff> your_return_type_if_present
yourfunction(args, enable_if_t<your condition, FOO> = BAR) {
    // ...
}

Scenario 2 leaves the parameter unnamed. You could say ::type Dummy = BAR, but the name Dummy is irrelevant, and giving it a name is likely to trigger an unreferenced parameter warning. You have to choose a FOO function parameter type and BAR default argument. You could say int and 0, but then users of your code could accidentally pass to the function an extra integer that would be ignored. Instead, we recommend that you use void ** and either 0 or nullptr because almost nothing is convertible to void **:

template <your_stuff> your_return_type_if_present 
yourfunction(args, typename enable_if<your_condition, void **>::type=nullptr) {
 // ...
}

If scenario 2 leaves the parameter unnamed then in what can it be used? Is there a way to make a code like this work with enable_if?

enum otype {oadd,omull};
template<otype o>
int add(int num1, std::enable_if<o == oadd, int>::type int= num2)
{
    if (o == omull) return num1 * num1;
    if (o == oadd ) return num1 + num2;
 }
like image 901
Oz Le Avatar asked Mar 13 '15 19:03

Oz Le


2 Answers

Microsoft's documentation there there is as clear as mud. Use this instead.

Providing a function template with an unnamed default parameter of the form:

typename enable_if<your_condition, void **>::type = nullptr

(as the MS scribe suggests), is useful in case - and only in case - you wish to write multiple overloads of the function template with different behaviours that are controlled by one or more of the template arguments. Then, by replacing your_condition with a condition expressing an appropriate requirement on the template argument(s), you can enlist the SFINAE principle to select the specific overload that you want to be instantiated for given template arguments.

The SFINAE parameter - let's call it that - is unused by the instantiated function; it exists solely to provoke SFINAE in function template overload resolution. Hence it can be nameless, and hence it must be defaulted: it must not force you to provide an additional, useless, argument when you invoke the function template.

For example:

#include <type_traits>
#include <iostream>

template <typename T>
T foo(T && t, 
    typename std::enable_if<std::is_same<T,int>::value, void **>::type = nullptr)
{
    std::cout << "Doubling " << t << " gives " << (t + t) << std::endl;
    return t + t; 
}

template <typename T>
T foo(T && t, 
    typename std::enable_if<!std::is_same<T,int>::value, void **>::type = nullptr)
{
    std::cout << "Squaring " << t << " gives " << (t * t) << std::endl;
    return t * t; 
}

using namespace std;

int main()
{
    cout << foo(2) << endl;
    cout << foo(3.3) << endl;
    return 0;
}

Output is:

Doubling 2 gives 4
4
Squaring 3.3 gives 10.89
10.89

In these two overloads of function template foo, the first one doubles it's type T argument and the second one squares its argument, and a SFINAE parameter is used to determine that the doubling overload will be instantiated if T is int and that the squaring overload will be chosen otherwise.

When T is int, the condition:

!std::is_same<T,int>::value

that controls the SFINAE parameter of the squaring overload is false. Consequently the type specifier:

typename std::enable_if<!std::is_same<T,int>::value, void **>::type = nullptr

fails to compile. That is a substitution failure in template resolution. Substituting int for T in the squaring overload is not viable. So the squaring overload is eliminated from the running, and only the doubling overload is left to instantiate the function call.

When T is (say) double and not int, then exactly the opposite happens and only the squaring overload survives template resolution. Call foo(2) and you get doubling. Call foo(3.3) and you get squaring.

MS's specimen SFINAE parameter here is needlessly lengthy.

template< bool B, class T = void >
struct enable_if;

as per C++11 Standard and later, defaults T to void. So the like of:

typename std::enable_if<some_condition, void **>::type = nullptr

can as well be abbreviated to:

typename std::enable_if<some_condition>::type * = nullptr

And as of C++14 the Standard has:

template< bool B, class T = void >
using enable_if_t = typename enable_if<B,T>::type

So the same SFINAE parameter can be further shortened to:

std::enable_if_t<some_condition> * = nullptr

Applying a SFINAE function template parameter to the case that you have gestured at in your post, you would write the like of:

enum ops {
    add,
    multiply
};

template<ops Op>
int op(int const & lhs, int const & rhs, 
        std::enable_if_t<Op == add> * = nullptr)
{
    return lhs + rhs;
}

template<ops Op>
int op(int const & lhs, int const & rhs, 
        std::enable_if_t<Op == multiply> * = nullptr)
{
    return lhs * rhs;
}

...

auto i = op<add>(2,3);
auto j = op<multiply>(2,3);

...
// C++14
like image 140
Mike Kinghan Avatar answered Sep 20 '22 01:09

Mike Kinghan


enable_if examples (if it helps):

For functions with non-void return type:

For single condition:

template <template T, typename std::enable_if<!std::is_same<T,std::string>::value>::type* = nullptr >
T func(T x){}

For multiple condition:

template <template T, typename std::enable_if<!std::is_same<T,std::string>::value &&!std::is_same<T,int>::value>::type* = nullptr >
T func(T x){}


For functions with void return type:

For single condition:

template <template T>
typename std::enable_if<!std::is_same<T,std::string>::value>::type
func(T x){}

For multiple condition:

template <template T>
typename std::enable_if<!std::is_same<T,std::string>::value &&!std::is_same<T,int>::value>::type
func(T x){}

Don't forget to include #include <type_traits>

like image 38
Jahid Avatar answered Sep 24 '22 01:09

Jahid