2

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

Michel
  • 338
  • 1
  • 13
  • I found that I can use some kind of Functor class to approach the behaviour I'd like: https://godbolt.org/z/WsdnPGaPP – Michel Mar 10 '23 at 18:51

2 Answers2

3

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

JeJo
  • 30,635
  • 6
  • 49
  • 88
0

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.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • I think that's what I don't understand: why doesn't the compiler try to convert? It would if it wasn't a template function, I guess. Is it that template deduction happens first and fails, so there is no candidate function to try the conversion? – Michel Mar 10 '23 at 19:03
  • @Michel Yes. Template deduction doesn't do conversions because there is essentially an infinite number of types the parameter could be converted into. – NathanOliver Mar 10 '23 at 19:08
  • Thanks, that makes sense :) It makes me wonder if we should have deduction guides for function templates: it works for a constructor if a deduction guide is provided, and a constructor is a kind of function~ Would be nice to have a generalization of that. – Michel Mar 10 '23 at 19:14
  • @Michel We pretty much already have that, it's called overloading. You can leverage overloading like `void someFunc(const char* str) { someFunc(Wrapper{str}); }` – NathanOliver Mar 10 '23 at 19:20