I'm trying to understand universal references and std::enable_if
better, but I'm a little stuck as to what's going on here in my code.
First off, I've noticed people seem to use std::enable_if
in two different ways:
template<typename T, std::enable_if<condition, T>::type* = nullptr>
or something similar to that.
template<typename T> std::enable_if_t<condition, T> myfunc() {...}
or something similar to that.
I understand what's happening in the second, but I'm confused about why anyone would use the first. What does that achieve except add another parameter to the template? Is it an SFINAE thing?
I'm also stuck on universal references when using enable_if
. Here is my code and the results I'm getting. Note that I'm using Howard Hinnant's type printing code from "Is it possible to print a variable's type in standard C++?", which I'll omit here for brevity.
Anyways, the function conditionless
seems to work fine with everything.
I'm very confused about is_integral
and decay
, which you can see at thhe beginning of main
. I get the output:
true: unsigned long
false: unsigned long
false: unsigned long
false: unsigned long
and I have no idea why the last three are false.
Then I have the issues (marked 1 and 2 in the source below) where when using enable_if
in either of the two ways mentioned above, they refuse to compile when accepting an lvalue of an integral or floating point type.
Headers and type printing code omitted for brevity:
template<typename T>
void conditionless(T&& val) {
std::cout << "conditionless(" << val << ")\n";
}
template<typename T, typename std::enable_if<std::is_integral_v<T>, T>::type* = nullptr>
void outputIntType(T&& val) {
std::cout << "outputIntType(" << val << ")\n";
}
template<typename T>
typename std::enable_if_t<std::is_floating_point_v<T>>
outputFloatType(T&& val) {
std::cout << "outputFloatType(" << val << ")\n";
}
int main() {
size_t sz = 1;
size_t &ref = sz;
// All of these report as having type "unsigned long", but for some reason, the first reports true for is_integral, and
// the other three report false.
std::cout << std::boolalpha << std::is_integral_v<decltype(sz)> << ": " << type_name<decltype(sz)>() << '\n';
std::cout << std::boolalpha << std::is_integral_v<std::decay<decltype(sz)>> << ": " << type_name<std::decay<decltype(sz)>::type>() << '\n';
std::cout << std::boolalpha << std::is_integral_v<decltype(ref)> << ": " << type_name<decltype(sz)>() << '\n';
std::cout << std::boolalpha << std::is_integral_v<std::decay<decltype(ref)>> << ": " << type_name<std::decay<decltype(ref)>::type>() <<'\n';
// This works fine.
conditionless(sz);
conditionless(2UL);
conditionless(2L + 1);
// ******* 1 *******
// This fails and claims no matching function call to outputIntType(size_t&)
// template argument deduction / substitution failed:
// error: no type named 'type' in 'struct std::enable_if<false, long unisgned int&>'
// I'm particularly confused about why the is_integral evaluates to false.
//outputIntType(sz);
// These work fine.
outputIntType(2UL);
outputIntType(2L + 1);
double pi = 3.1415926535;
// These work fine.
conditionless(pi);
conditionless(2 * pi);
conditionless(0.00000001);
// ******* 2 *******
// This fails as well:
// main.cpp: In function 'int main()':
// error: no matching function for call to 'outputFloatType(double&)'
// note: candidate: 'template<class T> std::enable_if_t<is_floating_point_v<T> > outputFloatType(T&&)'
// template argument deduction/substitution failed:
// outputFloatType(pi);
// These work fine.
outputFloatType(2 * pi);
outputFloatType(0.00000001);
}
Any insight that anyone could give me on the two different uses of enable_if
and why my code with enable_if
refuses to accept lvalues would be greatly appreciated.
I'm trying to understand universal references
Use of that term is discouraged. The official term is "forwarding references".
I understand what's happening in the second, but I'm confused about why anyone would use the first. What does that achieve except add another parameter to the template? Is it an SFINAE thing?
All enable_if_t<B, T>
does is evaluate to T
if B == true
, otherwise it produces invalid code. Invalid code produced during substitution doesn't lead to a compilation error (SFINAE).
It doesn't matter where enable_if_t
appears as long as it's affected by the substitution step (e.g. can be in the return type, parameter list, template parameter list, ...).
and I have no idea why the last three are false.
You forgot to access ::type
in your std::decay
transformation. You are comparing the trait itself, not its result.
they refuse to compile when accepting an lvalue of an integral or floating point type.
There is a special rule regarding deduction of forwarding references in the Standard. Given a forwarding reference parameter T&&
, T
will be deduced as an lvalue reference if the function is called with an lvalue.
You need to take this into account in your traits:
typename std::enable_if_t<std::is_floating_point_v<std::remove_reference_t<T>>>