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.
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.
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.