Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't overload resolution pick the first function?

Tags:

c++

c++11

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.

like image 735
Me myself and I Avatar asked Jan 10 '14 22:01

Me myself and I


People also ask

What is overload resolution?

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.

What happens when you overload a function?

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.

Is it possible to overload the function templates?

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.

When compiler decides binding of an overloaded member then it is called?

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.


2 Answers

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

like image 145
Daniel Frey Avatar answered Oct 19 '22 23:10

Daniel Frey


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.

like image 34
Casey Avatar answered Oct 20 '22 00:10

Casey