Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Template deduction guide for function?

I am trying to write some templated functions that accept either a std::basic_string or a char array from which the basic_string could be constructed.

My current solution is:

#include <string>

template<typename CharT>
void foo(std::basic_string<CharT> str)
{
    (void)str; // do something with str
}
template<typename CharT>
void foo(CharT const * arr)
{
    return foo(std::basic_string<CharT>{arr});
}

int main(void)
{
    foo("hello");
    foo(std::string{ "hello" });
    foo(L"hello");
    foo(std::wstring{ L"hello" });
}

But this means that for each function I have to write another function that calls the first one. That's pretty annoying; is there an easier way to do it? Maybe it could be a template deduction guide, but as far as I know it does not exist for functions, only classes.

The first templated function is not sufficient because the template deduction fails: the compiler cannot deduce CharT in std::basic_string<CharT> from CharT const *. That is why I need a simpler way to tell this to the compiler.

like image 223
Boiethios Avatar asked Mar 14 '17 07:03

Boiethios


2 Answers

Just bite the bullet and use 2 overloads. Any clever solution (which as davidhigh showed do exist) will only add unnecessary complexity, potential for bugs and confusion to the next reader.

You only write once but read multiple times. The small incovenient of writing a 1 line of body overload is worth doing against an non-idiomatic convoluted smart way.

Don't get me wrong, I love to find these smart solutions in C++, but if I found this solution in a production code it would take me a few good minutes just to figure out what the heck it is and what does it do, only to find out it just does what should have been a very basic thing in a complex way, I would ... well let's just say I wouldn't say nice things about the author of the code. Being lazy when writing code will cost you times over when you maintain, debug, extend, or even use the code.

Write simple, idiomatic and easy to understand code!

like image 186
bolov Avatar answered Sep 27 '22 19:09

bolov


After a bit more of research, the best option imo is to use the C++17 feature std::basic_string_view:

template<typename CharT>
void foo(std::basic_string_view<CharT> str)
{
    (void)str; // do something with str ...
               // while remembering that string_view does not own the string
}

So forget about the older explanation below if you have access to a C++17-compiler.



There are two cases here to consider. The first case is that you do not really want to do something special with the basic string, but rather you apply only methods also available for the char-array (and just want to make sure it's called correctly regardless of the parameters). In this case, I'd simply use a general template parameter:

template<typename string_type
        /* possibly some SFINAE to allow/disallow certain types */>
auto foo(string_type s)
{
    std::cout << s << std::endl;
}

Second case is that you really want to do some special operation on the string which is not present for the char array. In this case you need an overload for basic_string, but you probably want to write it only once and not for each and every function you use. This is what the following string_invoker class tries to do (but it still needs some improvement, just working on it):

template<typename method>
struct string_invoker_impl
{
    string_invoker_impl(method m) : m(m) {}

    template<typename CharT>
    auto operator()(std::basic_string<CharT> str) const
    {
        return m(str);
    }

    template<typename CharT>
    auto operator()(CharT const * arr) const
    {
        return operator()(std::basic_string<CharT>{arr});
    }

    //possibly further methods for non-const array's, modification, etc.    

    method m;
};

auto string_invoker = [](auto m) { return string_invoker_impl<decltype(m)>{m}; };

auto foo_impl = [](auto str) {std::cout<< str <<std::endl; };
auto foo = string_invoker(foo_impl);

//you  can merge the previous two calls also in a single one:
//auto foo = string_invoker( [](auto str) {std::cout<< str <<std::endl; });


int main(void)
{
    foo("hello");
    foo(std::string{ "hello" });
    //foo(L"hello");                      //need std::wcout, thus it fails with std::cout
                                          //but it's no general problem, just overload your foo_impl function
    //foo(std::wstring{ L"hello" });
}

DEMO

like image 23
davidhigh Avatar answered Sep 27 '22 17:09

davidhigh