Is it possible to get a template function to deduce the type of a template argument using either conversion or deduction guide?

82 views Asked by At

Is it possible to get the compiler to deduce the type for the template function call, using either the type conversion or the deduction guide? And, if not, why not?

#include <iostream>

template<typename T>
class Wrapper
{
public:
    Wrapper(T&& elmt)
        : m_data(std::forward<T>(elmt))
    {   }

    const T& data() const
    {
        return m_data;
    }

private:
    T m_data;
};

template<typename T>
Wrapper(T&& elmt) -> Wrapper<T>;

template<typename T>
void someFunc(const Wrapper<T>& wrapper)
{
    std::cout << wrapper.data() << std::endl;
}

int main()
{
    // Any chance this could work?
    // someFunc("Make me a Wrapper<const char*> please!");     //fails
    // someFunc({"Make me a Wrapper<const char*> please!"});   //fails

    // This works, but that's what I'd like to avoid
    someFunc(Wrapper{"This one works"});

    return 0;
}

(Compiler Explorer link: https://godbolt.org/z/eGs3raMY4)

If Wrapper wasn't a template, this would work directly:

#include <iostream>

class StrWrapper
{
public:
    StrWrapper(const char* str)
        : m_data(str)
    {   }

    const char* data() const { return m_data; }

private:
    const char* m_data;
};

void strFunc(const StrWrapper& wrapper)
{
    std::cout << wrapper.data() << std::endl;
}

int main()
{
    strFunc("This works, right?");

    return 0;
}

(Compiler Explorer: https://godbolt.org/z/nnoaPcs91)

I know I could add an overload for each type I want the deduction for, but in this case it isn't a very practical solution (many overloads required).

2

There are 2 answers

0
JeJo On BEST ANSWER

Is it possible to get the compiler to deduce the type for the template function call, using either the type conversion or the deduction guide?

You can do a recursive approach here

// variable template for checking the "Wrapper<T>" type
template<typename T> inline constexpr bool is_Wrapper = false;
template<typename T> inline constexpr bool is_Wrapper<Wrapper<T>> = true;

template<typename T>
void someFunc(T&& arg)
{
    if constexpr (is_Wrapper<T>)  // if T == Wrapper<T>
        std::cout << arg.data() << std::endl;
    else  // if T != Wrapper<T>, construct explicitly and pass to the someFunc()
        someFunc(Wrapper<T>{ std::forward<T>(arg) });
}

This allows the both

someFunc("Make me a Wrapper<const char*> please!");  // works
someFunc(Wrapper{ "This one works" });               // works

See a live demo in godbolt.org

4
NathanOliver On

Instead of having someFunc take a wrapper you can have it take T instead and then construct a wrapper inside the function like

template<typename T>
void someFunc(T&& to_wrap)
{
    Wrapper wrapper{std::forward<T>(to_wrap)};
    std::cout << wrapper.data() << std::endl;
}

Which allows

someFunc("Make me a Wrapper<const char*> please!"); 

to compile as seen in this live example.


The reason both

someFunc("Make me a Wrapper<const char*> please!");
someFunc({"Make me a Wrapper<const char*> please!"});

fail is that they are not Wrapper's, and the compiler will not try to convert the arguments, so there is no way for the compiler to deduce what T should be.