Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

'if' with template parameters or SFINAE is preferred?

Tags:

Preferred is this:

template<typename T> bool isNotZero(const T &a) {     if (std::is_floating_point<T>::value) return abs(a) > std::numeric_limits<T>::epsilon();     else return a; } 

Or this:?

template<typename T> std::enable_if<std::is_floating_point<T>::value, bool>::type isNotZero(const T &a) { return abs(a) > std::numeric_limits<T>::epsilon(); }  template<typename T> std::enable_if<std::is_integral<T>::value, bool>::type isNotZero(const T &a) { return a; } 

I usually use the first type to avoid many versions of function.

I believe it is exactly the same.

The first version optimized in opcode stage and the second version in template instantiation stage.

like image 269
Chameleon Avatar asked Apr 23 '13 20:04

Chameleon


People also ask

Why do we use template template parameter?

Why we use :: template-template parameter? Explanation: It is used to adapt a policy into binary ones.

What is Sfinae used for?

One of the primary uses of SFINAE can be found through enable_if expressions. enable_if is a set of tools, available in the Standard Library since C++11, that internally use SFINAE. They allow to include or exclude overloads from possible function templates or class template specialization.

What is correct for template parameter?

A template argument for a template template parameter is the name of a class template. When the compiler tries to find a template to match the template template argument, it only considers primary class templates. (A primary template is the template that is being specialized.)

Will concepts replace Sfinae?

So the simple answer is YES.


2 Answers

I believe it is exactly the same.

I would not say it is exactly the same.

In the first version, you are using a conditional statement which is evaluated at run-time, but the condition that decides which branch shall be executed can be (and is) decided at compile-time.

Therefore, both of your branches must compile and will be compiled no matter what the type of the input is, even though we known at compile-time that only one of them will be executed and the other will be dead - I expect the compiler would issue a warning here.

In the second case, you only compile (and execute, of course) what is appropriate for the type of the input. In my opinion, this makes the second approach superior.

Now even though in this particular situation it will likely make no difference which approach you choose, conditional executions that are decided by compile-time conditions should be expressed by means of compile-time constructs - SFINAE and template overloading, while if should be used for conditions that depend on the run-time state of the system.

The first approach would not be possible, for instance, if the two branches of the conditional contained code that compiles only when the corresponding branch is executed. Consider these two types:

struct X {     X(int) { } };  struct Y {     Y() { } }; 

And the following function template:

template<typename T> T foo(const T &a) {     if (std::is_constructible<T, int>::value)     {         return T(42);     }     else     {         return T();     } } 

Now none of the following calls would be legal:

foo(X()); // ERROR! X is not default-constructible foo(Y()); // ERROR! Y is not constructible from an int 

This alone suggests that, in general, the appropriate tool for handling compile-time conditional execution is template overloading + SFINAE (or equivalent constructs possibly involving class template specializations).

Sure, there are degenerate cases (such as this one) that allow you using other tools, but if we are looking for conceptually correct design guidelines, I believe there is a clear winner here.

Things would of course be different if something like static if existed in C++, but this is not the case at the moment - and not even in a near future, it seems.

like image 118
Andy Prowl Avatar answered Oct 22 '22 02:10

Andy Prowl


Currently, I would prefer to use SFINAE. Using SFINAE does not require any optimization because you are explicitly allowing only one of the functions to be called depending on the situation. There is no optimization to perform simply because the appropriate function will be called at run-time with no decision to make.

Using an if conditional statement puts the onus on the program to make the decision at run-time. Of course, this could be optimized away, but may not be. It is likely to result in the compilation of both branches regardless of which one is actually executed for a specific template argument. This means that the code in each branch must be syntactically and semantically correct for any given template argument.

Maybe one day we will have a static if, which would allow you to write the if statement as a compile-time condition, but there are some strong feelings about this at the moment:

The static if feature recently proposed for C++ is fundamentally flawed, and its adoption would be a disaster for the language.

However, in the near future (aiming for a release around C++14) we may have constraints (a.k.a. concepts-lite), which would allow you to write the functions like so:

template<Floating_point T> bool isNotZero(const T &a) { return abs(a) > std::numeric_limits<T>::epsilon(); }  template<Integral T> bool isNotZero(const T &a) { return a; } 

Which is equivalent to writing:

template<typename T>   requires Floating_point<T>() bool isNotZero(const T &a) { return abs(a) > std::numeric_limits<T>::epsilon(); }  template<typename T>   requires Integral<T>() bool isNotZero(const T &a) { return a; } 

The Floating_point and Integral constraints are just constexpr predicates on their template arguments which are checked at compile-time and participate in overload resolution. This will be the preferred way to write such a set of functions.

like image 28
Joseph Mansfield Avatar answered Oct 22 '22 02:10

Joseph Mansfield