4

I'm writing a function to manipulate a set with it's iterators, and it's giving me the error

candidate template ignored: couldn't infer template argument

The minimum reproducible code is as follows:

#include <bits/stdc++.h>
using namespace std;

template <typename T>
void foo(typename set<T>::iterator it1)
{
    // do something
}

int main() 
{
    set<int> aSet;
    foo(aSet.begin()); // no matching function call to 'foo'
}

I tried including <set> directly, changing the order of function definitions, but nothing solved it. And the weirdest thing is that I used the set iterator as parameter in other functions, and it worked. These other function declarations were something like this:

template <typename T>
void bar(set<T>& aSet, vector<T>& aVector, typename set<T>::iterator it);

(these exact parameters, in this exact order).

JeJo
  • 30,635
  • 6
  • 49
  • 88

1 Answers1

3

In foo the T in argument typename set<T>::iterator is a non-deduced contexts:

...the types, templates, and non-type values that are used to compose P do not participate in template argument deduction, but instead use the template arguments that were either deduced elsewhere or explicitly specified. If a template parameter is used only in non-deduced contexts and is not explicitly specified, template argument deduction fails.

The T is used in this non-deduced contexts and does not explicitly specify in foo what type it should deduce to, the compiler can not deuce the type; hence the error!

That means, if you explicitly mention what T in foo, it will compile as mentioned in the quote.

foo<int>(aSet.begin()); // compiles!

or better, provide the iterator as template parameter

template <typename Iterator> 
void foo(Iterator it1)

If you want to restrict the Iterator only for std::set<T>::iterator, you can SFINAE function. Here is an example code.

#include <type_traits> // std::enable_if, std::is_same
#include <iterator>    // std::iterator_traits

template<typename Iterator>
inline constexpr bool is_std_set_iterator = 
std::is_same_v<Iterator, typename std::set<typename std::iterator_traits<Iterator>::value_type>::iterator> ||
std::is_same_v<Iterator, typename std::set<typename std::iterator_traits<Iterator>::value_type>::const_iterator>;

template <typename Iterator>
auto foo(Iterator it1) -> std::enable_if_t<is_std_set_iterator<Iterator>, void>
{
    // do something
}

On the other hand, in bar the compiler already deduced the first function argument to std::set<int>& aSet and sees the typename std::set<T>::iterator, which it can deduce to typename std::set<int>::iterator, because T was already deduced to int in the previous function argument. Hence, it works!


As a side note, see the followings:

JeJo
  • 30,635
  • 6
  • 49
  • 88
  • traits would miss `std::set` though. – Jarod42 Sep 09 '21 at 08:28
  • @Jarod42 Of course, we can not get the container type from the iterator easily. For shown code of OP, this will work. BTW, the traits are just add up from my side for the original OP's issue. There might be a better way to tackle the issue you mentioned. – JeJo Sep 09 '21 at 14:23
  • 1
    Thank you! That did it :) As a side note: I know I shouldn't include bits/stdc++ and use namespace std, but I'm studying competitive programming, so I'm doing it for the sake of fast writing. But I'll read these links nonetheless. – Gabriel Bergoc Sep 09 '21 at 14:26
  • I read the links you provided and now I am thourougly convinced to not use bits/stdc++ and "using namespace std" unless in very specific contexts. Thank you very much! – Gabriel Bergoc Sep 09 '21 at 17:05