Itachi Uchiwa
Itachi Uchiwa

Reputation: 3164

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";
}

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?

Upvotes: 2

Views: 664

Answers (2)

HTNW
HTNW

Reputation: 29193

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!)

Upvotes: 5

Sam Varshavchik
Sam Varshavchik

Reputation: 118300

The simplest solution is to use a forwarding reference:

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

And with this, count_(vs, "C++") will compile.

Note that the specialization will need to be adjusted, accordingly:

template <typename Value>
std::size_t count_(std::vector<char const*> const& vcp, Value && value)

// ...

You'll need to keep in mind that this template will now participate in overload resolution in more situations than before (due to the forwarding reference). If that's undesirable (if there are other overloads of count_) some additional work will be needed to have this template get excluded from overload resolution, when it won't compile.

Upvotes: 4

Related Questions