Search code examples
c++template-argument-deductionfunction-templates

Is there a way to force implicit conversion in function template argument deduction?


I have this count_ function template that counts the occurrences of a value in a vector of that its type:

template <typename T>
std::size_t count_(std::vector<T> const& vt, T const& value)
{
    std::cout << "count_(vector<T>, T const&)\n";
    std::size_t n{};
    for(auto const& e : vt)
        if(e == value)
            ++n;
    return n;
}

template <>
std::size_t count_(std::vector<char const*> const& vcp, char const * const& value)
{
    std::cout << "count_(vector<char const*>, char const*const&)\n";

    std::size_t n{};
    for(auto const& e : vcp)
        if( !strcmp(e, value))
            ++n;
    return n;
}



int main()
{

    std::vector<std::string> vs{"function", "C++", "template", "C++", "specialization", "partial", "C++", "full"};
    std::cout << count_(vs, std::string("C++")) << '\n';

    std::vector<double> vd{3.14, 5.2, 7.7, 3.14, 56.87, 3.14, 6.8798, 12.545};
    std::cout << count_(vd, 3.14) << '\n';

    std::cout << count_(std::vector{7, 24, 16, 7, 81, 7, 5, 7, 23, 10, 7, 15, 8}, 7) << '\n';

    std::vector<char const*> vcp{"function", "C++", "template", "C++", "specialization", "partial", "C++", "full"};
    std::cout << count_(vcp, static_cast<char const*>("C++")) << '\n';


    std::cout << "\ndone!\n";
}
  • The program works just fine however I always need to explicitly cast or pass the second argument to the type of the element type of the vector that is passed as the first argument.

  • I know this occurs due to the limited number or allowed implicit conversion in template Argument Deduction. And if count_ is an ordinary non-template function then it works OK without casting a literal string to a constant pointer to char.

So is there a better way to avoid such casts std::string("C++"), static_cast<char const*>("C++")...?

I guess there is something like "meta-programming" if so could you elaborate an example please?


Solution

  • Command the template argument to be deduced from the vector, but not from the value

    template<typename T> //                                             v      only required change       v
    typename std::vector<T>::size_type count_(std::vector<T> const &vt, typename std::vector<T>::value_type const &value) {
        std::cout << "count_(vector<T> const&, T const&)\n";
        typename std::vector<T>::size_type n = 0;
        for(auto const &e : vt) if(e == value) n++;
        return n;
    }
    

    Template parameter deduction basically cannot "see" through type aliases, especially not ones that are members of an as-yet unknown specialization. The second argument to count_ no longer participates in deducing T. Rather, the vector always decides T, and then the second argument is implicitly converted to its type. Your specialization remains a specialization. Actually, it doesn't change.

    // just cosmetic changes
    template<>
    std::vector<char const*>::size_type count_(std::vector<char const*> const &vcp, char const *const &value) {
        std::cout << "count_(vector<char const*> const&, char const *const&)\n";
        std::vector<char const*>::size_type n = 0;
        for(auto const &e : vcp) if(!std::strcmp(e, value)) n++;
        return n;
    }
    

    Godbolt

    Note: in this case we're "lucky" that std::vector<T>::value_type is a conveniently available alias for T. In general, you can use std::type_identity_t<T> as a template deduction blocker, but that's only available in C++20. (Of course, you can implement it yourself—it's only two lines!)