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>>>
Related
In the below code example, the second line
std::cout << "is_even (4.4) = " << std::boolalpha << is_even(4.4);
causes compiler failures because no proper substitution is found.
If there is an error, then how is this SFINAE? I thought std::enable_if is kind of SFINAE and the acronym means no failure if substitution can't be done. Where I am misinterpreting?
#include <iostream>
#include <type_traits>
template<class T, class = std::enable_if_t<std::is_integral_v<T>>>
bool is_even(T value)
{
return ((value % 2) == 0);
}
int main()
{
std::cout << "is_even (4) = " << std::boolalpha << is_even(4);
std::cout << "is_even (4.4) = " << std::boolalpha << is_even(4.4);
}
Please don't say what i expect in this situation to happen. I am just trying to see how should i see SFINAE here?
The error is caused by the call expression is_even(4.4). Since you have called a function is_even by passing a double, the definition of such a function must exist.
If we call any function, then its definition must exist somewhere. In your case, there is no function named is_even that takes a double parameter and hence the error.
SFINAE still works here. For example, when the template argument is deduced to be a double and then T is substituted with double the condition inside std::enable_if_t<std::is_integral_v<T>> evaluates to false. Thus this function template is ignored and no error is produced at this point. That is, the error that you're getting is not due to SFINAE. SFINAE doesn't mean that your program won't produce any error.
Now, for the call expression is_even(4.4) to work the compiler can't find any is_even that takes a double. And so it produced error. You can confirm this by overloading this function template with an ordinary function as shown below:
template<class T, class = std::enable_if_t<std::is_integral_v<T>>>
bool is_even(T value)
{
return ((value % 2) == 0);
}
//compiler will find this definition now and no error will be produced
bool is_even(double d)
{
return ((static_cast<int>(d) % 2) == 0);
}
Now for the call expression is_even(4.4) the compiler finds the overloaded is_even and hence produces no error.
Summary
The error that you're getting is not the result of SFINAE, instead it is due to the fact that the for the expression is_even(4.4) to work, we need the definition of a function that takes a double.
I am trying to use operator sizeof... to skip the function for the end of recursion by not calling it if there are no argument
#include<iostream>
template<typename T, typename ...Types>
void Display(T firstArg, Types...Args)
{
std::cout << firstArg << "\n";
std::cout << sizeof...(Types) << "\n";
std::cout << sizeof...(Args) << "\n";
if (sizeof...(Args) > 0)
Display(Args...);
}
int main()
{
Display(1, 2, 3,"hello");
return 0;
}
But I am getting following error for Display(Args...);
error C2780: 'void Display(T,Types...)': expects 2 arguments - 0
provided
Workaround is to add function for the end of recursion (which I want to avoid)
void Display()
{
}
Question is how to avoid end of recursion function
You can’t do this without some workaround, pre-C++17, unless you rewrite the function to not be recursive. The reason is that the entire function body is substituted, including branches of if statements that can never happen. That means that the compiler sees the call to Display() with no arguments, even though it would never have happened at runtime.
Since C++17, the solution to this is to use if constexpr instead of just if. That tells the compiler to evaluate the condition at compile time, and not try to compile the branch which doesn’t execute.
Note that “not try to compile” above is a simplification; the link has more details about what exactly is and isn’t done.
I've written a simple template to find the smallest number in a list of arguments.
template<typename T>
T smallerList(T a, T b) {
std::cout << "a= " << a << " b= " << b << std::endl;
return a < b ? a : b;
}
template<typename T, typename... Rest>
T smallerList(const T& param0, const Rest&... rest) {
T temp = smallerList(rest...);
return param0 < temp ? param0 : temp;
}
int main()
{
// Works, returns "3"
std::cout << "Smaller: " << smallerList(4, 5, 6, 3, 7) << std::endl;
// Sort of works, returns "2". Should be "2.14".
std::cout << "Smaller: " << smallerList(3.14, 43534, 100.2, 3.13, 2.14) << std::endl;
}
For some reason the second function call returns 2 instead of 2.14. Why does this happen?
I've printed the intermediate values of the variables and they are correct. It seems like an implicit conversion is happening when smallerList returns.
I was able to solve this by changing the line
T temp = smallerList(rest...);
into
T temp = smallerList<T>(rest...);
After this change the function prints 2.14 as expected.
My question: Why do I need to specify the type? I thought that templates functions are "created" for each type that is called?
Ok, I don't know how to really help you, because I don't know what exact logic you want to have. But at you variadic template you allow type mixing, and in your 2nd vector, you pass one integer - 43534, so when 2.14 back propagates when recursion rolls back you get something like
return (int)(43534 < 2.14? 43534 : 2.14);
Because 43534 will be param0, and you take return type of param0, and 2.14 gets converted to 2. Next it gets converted back to a float but you don't see it.
You either need to check whether the types of the parameters are the same, or come up with some logic to promote your arguments. Not that it does work like you expect if you use 43534.0 because it won't be an int anymore.
EDIT:
T temp = smallerList<T>(rest...); this does not really help you, it changes the behaviour, forcing a cast on each argument to type of the 1st argument. Well it gets more consistent. But try:
smallerList(7, 10.5, 10, 3.13, 2.14)
It will break. I am not 100% sure why, but I guess it couldn't match end of recursion, because it would look for smallerList(int, float) and your terminator template won't match.
You would need something like:
template<typename T, typename U>
T smallerList(T a, U b) {
std::cout << "a= " << a << " b= " << b << std::endl;
return a < b ? a : b;
}
It also discards 2nd type, so you will have type conversions, but if your goal is to keep the the type of 1st argument it is consistent.
I've obviously misunderstood the code. Thanks for pointing out the error.
--original post--
I know for a while that integers can be used as types in c++ template programming. What surprises me is that when an integer such as 2 and 3 is used as a type, one can actually instantiate a variable out of the type, as shown in the example below extracted from gcc 4.8.1 random.h.
It seems to me that one can declare a variable
2 x; //not actual c++ code
and x will be an integer variable initialized to the value of 2.
Code for deterniming whether a number is a power of 2:
#include <iostream>
template<typename _Tp> inline bool
_Power_of_2(_Tp __x) {
return ((__x - 1) & __x) == 0;
};
int main() {
std::cout << _Power_of_2(2) << std::endl;
std::cout << _Power_of_2(3) << std::endl;
}
Output:
1
0
Can someone please explain what's going on here in terms of types and domains?
Are there any similar features in other programming languages that can do the same, i.e. using a concrete value as a type?
Also, is this feature available for other types, such as struct, string or float?
Thanks,
In your example, 2 is not used as a type, but as a function parameter. From that, the template parameter _tP is automatically deduced as int. So the lines inside main would be equivalent to:
std::cout << _Power_of_2<int>(2) << std::endl;
std::cout << _Power_of_2<int>(3) << std::endl;
I am trying to write a simple function that will get me a number from user input within a range.
When instanciating this function i explicitly tell it i want it instanciated with int but still i get the error:
thermo.cpp:105:31: error: no matching function for call to ‘getInput(int&)’
Why is is trying to find a function that takes int& as argument?
template<class T, T min = std::numeric_limits<T>::min, T max = std::numeric_limits<T>::max>
T getInput(T default_value = T()){
std::string input;
T myNumber = T(); //default inits
while(true){
getline(cin, input);
if(input.length() == 0){
return default_value;
}
// This code converts from string to number safely.
stringstream myStream(input);
if (myStream >> myNumber){
if(myNumber > max || myNumber < min){
stringstream ss;
ss << "Input out of bounds. Received " << myNumber << " expected between " << min << " and " << max << ".";
throw invalid_argument(ss.str());
}
return myNumber;
}
cout << "Invalid number, please try again" << endl;
}
}
void get(const std::string& prompt, int& param){
cout << prompt << " [" << param << "]:";
param = getInput<int,0>(param); // i specifically tell it i want 'int', why am i getting 'int&'?
}
Update
If i try CharlesB suggestion:
void get(const std::string& prompt, int& param){
cout << prompt << " [" << param << "]:";
param = getInput<int,0>(int(param));
}
i get
thermo.cpp:105:36: error: no matching function for call to ‘getInput(int)’
Forgot:
g++ 4.5.3 under cygwin
Command line:
$ g++ thermo.cpp -o thermo.exe -Wall -pedantic -std=c++0x
Update 2
if i call it like this
void get(const std::string& prompt, int& param){
cout << prompt << " [" << param << "]:";
param = getInput<int,0,15>(int(param)); // fully parameterized
}
it works... but i'd rather not specify an upper bound (not even numeric_limits) on each call.
Don't use templates for min and max:
template<class T>
T getInput(T default_value = T(), T min = std::numeric_limits<T>::min(), T max = std::numeric_limits<T>::max());
There is no reason to use templates for those arguments(besides the fact that it does not work).
Edit: You can't use those arguments as template values since std::numeric_limits<T>::min() is a function, its value is known on runtime, and template value arguments have to be bound to a value at compile time. This is valid:
template<class T, T min = 0, T max = 5>
T getInput(T default_value);
Since 0 and 5 are known during compilation.
I don't know if this is the issue, but I can't imagine it's helping. This line:
template<class T, T min = std::numeric_limits<T>::min, T max = std::numeric_limits<T>::max>
...is using min/max as values, when they're really functions. Maybe that's confusing the template parameters?
The error code does not mean what you think. The error code is a shorthand for:
no matching function call to getInput that takes an int modifiable lvalue expression as the single argument
Where int modifiable lvalue expression is the type of the expression that you are using to make the call in this case param. Now the problem is that output of error codes in this format is that it is very verbose and it would become very hard to read with just two or three arguments of non trivial types, so the compiler condenses the error report and tells you:
no matching function call to getInput(int&), note that here int& is not the type of the function that will be called, as the compiler was unable to find such a function, but rather it is the type of the argument that is being used in the call.
If you perform the change that CharlesB suggests, then you will get a different error message saying that it cannot find getInput(int). The difference here is that int(param) creates a temporary (rvalue expression), so the error now reflects it. The need for a different error code comes from the fact that if you had a getInput(int&) function, in this second case, that overload cannot be used.
On the reason why you are getting that error code the basic problem is that std::numeric_limits<T>::max is not of type T. Your problem is the very base of SFINAE: You have defined a template that takes as second and third arguments T, and that T should be initialized with std::numeric_limits<T>::min (and max). Now when the compiler tries to determine the function to call, it will find that template, use T for int (you provided the exact type), 0 for the min and will then try to infer the last argument. At this point it will try to get a T value (last template argument) through the default template argument by substituting the known template arguments in: std::numeric_limits<T>::max. The problem is that std::numeric_limits<int>::max is not an int, but rather a static member function, so the types don't match, yielding a substitution failure. The language determins that substitution failure is not an error (SFINAE) and it only means that this template will be removed from the list of candidates for the function call. Because there is no other matching overload, the compiler gives up and tells you that it could not find a matching function for the call.
In C++11 you can use std::numeric_limits<T>::max(), as the function is marked as a const_expr and can thus be called to obtain a constant expression of type T that can be used as the template argument, but if you are working with a C++03 compiler, you will need to work around the problem in a different way, like moving the min and max to default arguments to a function, or providing different overloads that take values from the user or will call a function (default to std::numeric_limist<T>::max if the argument is not present, but this latter option is more cumbersome.
Template functions are instanciated with argument type, and param is a int&.
Rather do
param = getInput(int(param));
Also min and max can't be template arguments, a template argument is class, a typename or a POD.