In the following program, both function calls print "Non-integral overload" even though I have an enable_if
statement that restricts the function to integral container types only. Why is that?
#include <iostream>
#include <vector>
#include <type_traits>
template<bool B, typename V = void>
using enable_if = typename std::enable_if<B, V>::type;
template<typename ForwardIt>
auto f(ForwardIt first, ForwardIt)
-> enable_if<std::is_integral<decltype(*first)>{}>
{
std::cout << "Integral container type" << std::endl;
}
template<typename ForwardIt>
void f(ForwardIt, ForwardIt)
{
std::cout << "Non-integral container type" << std::endl;
}
int main()
{
struct X { };
std::vector<int> iv;
std::vector<X> xv;
f(iv.begin(), iv.end()); // "Non-integral container type"
f(xv.begin(), xv.end()); // "Non-integral container type"
}
I've even tried using enable_if<!std::is_integral<...>>
on the second overload but to no avail.
The process of selecting the most appropriate overloaded function or operator is called overload resolution. Suppose that f is an overloaded function name. When you call the overloaded function f() , the compiler creates a set of candidate functions.
Calls to an overloaded function will run a specific implementation of that function appropriate to the context of the call, allowing one function call to perform different tasks depending on context. For example, doTask() and doTask(object o) are overloaded functions.
You may overload a function template either by a non-template function or by another function template. The function call f(1, 2) could match the argument types of both the template function and the non-template function.
Method Overloading and Operator Overloading are examples of the same. It is known as Early Binding because the compiler is aware of the functions with same name and also which overloaded function is tobe called is known at compile time.
The other answer already explained the problem, but I think there is a better solution to it.
If you want to extract the type that an iterator type points to, you should use the iterator_traits
. In your code, change the first overload to:
template<typename ForwardIt>
auto f(ForwardIt first, ForwardIt)
-> enable_if<std::is_integral<typename std::iterator_traits<ForwardIt>::value_type>{}>
{
std::cout << "Integral container type" << std::endl;
}
and use the same with an additional !
on the second. This is more descriptive as the code is quite clear as to what it does.
Live example
For an iterator type foo
, decltype(*foo)
is going to be foo::value_type&
. A reference type is definitely not integral. You need to remove the reference (and possibly cv-qualification as well, IIRC) before evaluating the type with the std::is_integral
trait, which is easily done with the std::decay
transformation type trait:
template<bool B, typename V = void>
using enable_if = typename std::enable_if<B, V>::type;
template<typename T>
using decay = typename std::decay<T>::type;
template<typename ForwardIt>
auto f(ForwardIt first, ForwardIt)
-> enable_if<std::is_integral<decay<decltype(*first)>>{}>
{
std::cout << "Integral container type" << std::endl;
}
This will result in ambiguity with your other overload since both will now match. You will need to constrain the second overload as you suggest in the OP.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With