2

I'm writing function working with STL containers that have iterator.
And I'm trying to handle container that doesn't.

my template function:

template <typename T>
void    easyfind(...)
{
    throw std::invalid_argument("No iterator");
}

template <typename T>
typename T::iterator    easyfind(T& cont, int tofind)
{
    typename T::iterator    iter;

    iter = std::find(cont.begin(), cont.end(), tofind);
    if (iter == cont.end())
        throw std::out_of_range("Cannot find");
    return iter;
}

main.cpp:

// @ list
{
    std::list<int> L;
    std::list<int>::iterator iter;
    L.push_back(5);
    L.push_back(6);
    L.push_back(7);

    try
    {
        iter = easyfind(L, 7);
        std::cout << "found " << *iter << std::endl;
    }
    catch (std::exception& e)
    {
        std::cout << e.what() << std::endl;
    }
}

// @ stack ( no iterator )
{
    std::stack<int> S; 
    S.push(10);
    S.push(11);
    S.push(12);

    try
    {
        easyfind(S, 12);  // expect fallback case
    }
    catch (std::exception& e)
    {
        std::cout << e.what() << std::endl;
    }
}

I thought easyfind with stack would call void easyfind(...), but:

main.cpp:88:4: error: no matching function for call to 'easyfind'
                        easyfind(S, 12);
                        ^~~~~~~~
./easyfind.hpp:21:6: note: candidate template ignored: couldn't infer template argument 'T'
void    easyfind(...);
        ^
./easyfind.hpp:27:22: note: candidate template ignored: substitution failure [with T = std::stack<int>]: no type named 'iterator' in 'std::stack<int>'
typename T::iterator    easyfind(T& cont, int tofind);

The second ignore is what I expected, but i don't understand why it cannot call fallback function. What am I missing?

mishy
  • 123
  • 10

1 Answers1

1

T is not used as a function argument so it can't deduce what T should be. In this case you could replace the varargs function with a variadic template:

template <class... Args>
void easyfind(Args&&...) // Now Args... can be deduced
{
    throw std::invalid_argument("No iterator");
}

However, your current check excludes plain arrays, like int A[3];, and it also throws an exception in runtime instead of generating a clear error message at compile time. You could add a type trait to check if std::begin(container) is valid.

#include <iterator>
#include <type_traits>

template<class T>
struct has_iterator {
    static std::false_type test(...); // fallback
    
    template<class U>                 // matches if std::begin() is valid
    static auto test(const U& u) -> decltype(std::begin(u), std::true_type{});

    static constexpr bool value = decltype(test(std::declval<T>()))::value;
};

template<class T>
inline constexpr bool has_iterator_v = has_iterator<T>::value;

Your easyfind could then use that type trait in a static_assert instead of adding a template overload and throwing in runtime.

template <class T, class F>
auto easyfind(T&& cont, F&& tofind) {
    // emit a clear compile time error message about the problem:
    static_assert(has_iterator_v<T>, "No iterator");

    auto iter = std::find(std::begin(cont), std::end(cont), std::forward<F>(tofind));
    if (iter == std::end(cont)) throw std::out_of_range("Cannot find");
    return iter;
}

Demo

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • but I saw some code like [here](https://stackoverflow.com/questions/35213658/how-does-this-implementation-of-stdis-class-work), `template two test(...); ` works, with not using `T`. What is the difference? – mishy Feb 19 '22 at 04:51
  • +) without `template `, error: cannot pass object of non-POD type 'std::stack' through variadic function; call will abort at runtime [-Wnon-pod-varargs] – mishy Feb 19 '22 at 04:52
  • 1
    @mishy Instead of using varargs, use a variadic template like my second version uses. That way you don't get the `-Wnon-pod-varargs` warning. – Ted Lyngmo Feb 19 '22 at 05:05
  • 1
    @mishy I looked at the link now too. In their test, they supply the template parameter so it doesn't have to be deduced: `sizeof(detail::test(0))==1` - if you remove `T` there, it won't compile either. – Ted Lyngmo Feb 19 '22 at 05:28
  • It was my assignment that allows only c++98. So cannot use variadic templates... but your answer cleared up my question. Thanks. – mishy Feb 20 '22 at 07:56
  • @mishy Aha, I see. You're welcome! – Ted Lyngmo Feb 20 '22 at 11:56