template<typename T, size_t M, size_t K, size_t N, typename std::enable_if_t<std::is_floating_point<T>::value, T> = 0>
void fastor2d(){//...}
I copied this line of code from cpp-reference(only the std::enable_if
part, i do need T
and all three of the size_t
's), because i would like to use this function only when floating_types are used on it ... it does not compile.
Could somebody explain to me, why, and what it even does? While i am at it, how do you call this function afterwards?
Every tutorial or question here on SO gets bombed with answers, and that is great, but to someone who does not understand jacks*** of what is happening, even those are not really helpful.(sry, if possibly slightly agitated or aggressive)
EDIT: i greatly appreciate all answers as of now, i realize that my wording might have been a bit off ... i understand what a template parameter is, and know the difference between runtime and compiletime etc, but i just cant get a good grasp of the syntax behind std::enable_if
EDIT2:
template<typename T, size_t M, size_t K, size_t N, typename = std::enable_if_t<std::is_integral<T>::value>>
void fastor2d(){
Fastor::Tensor<T,M,K> A; A.randInt();
}
This is literally the only thing i need changed. Notice the random() part
template<typename T, size_t M, size_t K, size_t N, typename = std::enable_if_t<std::is_floating_point<T>::value>>
void fastor2d(){
Fastor::Tensor<T,M,K> A; A.random();
}
Rather than a top-down approach starting with you code snippet, I'll take a bottom-up approach to explain some important details about templates and what tools and techniques are involved.
At heart, templates are a tool that let you write C++ code that applies to a range of possible types, not strictly for a fixed type. In a statically-typed language, this is firstly a great tool for reusing code without sacrificing type safety, but in C++ in particular, templates are very powerful because they can be specialized.
Every template declaration begins with the keyword
template
, and a list of type or non-type (i.e value) parameters. Type parameters use the special keywordtypename
orclass
, and are used to let your code work over a range of types. Non-type parameters simply use the name of an existing type, and these let you apply your code to a range of values that are known at compile-time.A very basic templated function might look like the following:
This lets us reuse code safely for a range of possible types, and we can use it as follows:
The compiler is even smart enough to deduce the template parameter
T
for each of these, so you can safely get away with the following, functionally identical code:But suppose you want
print
to behave differently for one type. Suppose, for example, you have a customPoint2D
class that needs special handling. You can do this with a template specialization:Now, anytime we use
print
withT=Point2D
, the specialization is chosen. This is really useful, for example, if the generic template just doesn't make sense for one specific type.But what if we want to specialize a template for many types at once, based on a simple condition? This is where things become a little meta. First, let's try to express a condition in a way that lets them be used inside templates. This can be a little tricky because we need compile-time answers.
The condition here will be that
T
is a floating point number, which is true ifT=float
orT=double
and false otherwise. This is actually fairly simple to achieve with template specialization alone.Now, we can query any type to see if it's a floating point number:
But how can we use this compile-time condition inside another template? How can we tell the compiler which template to choose when there are many possible template specializations to choose from?
This is achieved by taking advantage of a C++ rule called SFINAE, which in basic English, says, "when there are many possible templates, and the current one doesn't make sense*, just skip it and try the next one."
*There's a list of errors, when attempting to substitute template arguments into templated code, that cause the template to be ignored without an immediate compiler error. The list is a bit long and complex.
One possible way that a template doesn't make sense is if it tries to use a type that doesn't exist.
This is the exact same trick that
std::enable_if
uses under the hood.enable_if
is a template class accepting a typeT
and abool
condition, and it contains a nested typetype
equal toT
only when the condition is true. This is also pretty easy to achieve:Now we have a helper that we can use in place of any type. If the condition we pass is true, then we can safely use the nested type. If the condition we pass is false, then the template is no longer considered.
I completely agree that
std::enable_if<std::is_floating_point<T>::value, void>::type
is a messy way to spell out a type. You can read it as "void
if T is floating point, and meaningless nonsense otherwise."Finally, to take apart your example:
Note the
= 0
at the end. That's simply a default value for the final template parameter, and it lets you get away with specifyingT
,M
,K
, andN
but not the fifth parameter. Theenable_if
used here means that you can provide other templates calledfastor2d
, with their own sets of conditions.I'll try to explain this as simple as possible not to go into the language details too much since you asked for it.
Template arguments are compile time arguments (they do not change during the run-time of your application). Function arguments are run-time and have a memory address.
Calling this function would look something like this:
In the <> brackets you see the compile-time arguments or more accurately the template parameters, and the function in this case takes 0 runtime arguments in the () brackets. The last compile time argument has a default argument which is used to check whether the function should compile at all (enable_if type). If you want to know more clearly what enable if does you should search for the term SFINAE, which is a template metaprogramming technique used to determine whether a function or class should exist or not.
Here is a short SFINAE example:
The reason the third function call fails, is because the function does not exist. This is because the enable if caused the function not to exist when the compile-time bool that is passed in as its' template argument is false.
Do note that a lot of people agree that the SFINAE syntax is horrible and that a lot of SFINAE code will not be necessary anymore with the introduction of concepts and constraints in C++ 20.
First of all, I'll rewrite your function in a working form
The point is that I've changed the second template argument of
std::enable_if_t
formT
toint
.I've also removed the
typename
beforestd::enable_if_t
but isn't important: thetypename
is implicit in the_t
at the end ofstd::enable_if_t
, introduced from C++14. In C++11 the correct form isBut why it works?
Start from the name: SFINAE.
Is a short form for "Substitution Failure Is Not An Error".
It's a C++ rule so that when you write some thing as
and
I
is3
, the condition ofstd::enable_if_t
istrue
sostd::enable_if_t< I == 3, int>
is substituted withint
sofoo()
is enabled but whenI
isn't3
, the condition ofstd::enable_if_t
iffalse
sostd::enable_if_t< I == 3, int>
is not substituted sofoo()
isn't enabled but this ins't an error (if, through overloading, there is anotherfoo()
function, enabled, that matches the call, obviously).So where is the problem in your code?
The problem is that
std::enable_if_t
is substituted, when the first template parameter istrue
, with the second parameter.So if you write
and you call
the
std::is_floating_point<float>::value
(but you can also use the shorter formstd::is_floating_point_v<T>
(_v
and not::value
)) so the substitution take place and you getbut, unfortunately, a template value (not type) parameter can't be of type floating point, so you get an error.
If you use
int
instead ofT
, the substitution give youand this is correct.
Another solution can be use the following form
as suggested by Andreas Loanjoe, because the substitution give you
that is a valid syntax.
But this solution has the drawback that doesn't works when you want to write two alternative functions, as in the following example
where works the solution based on the value