Search code examples
c++c++20

How to specialise the function or convert the first arg of a function using a perfect forward?


I try to merge two functions into a more general function:

template<class ...T>
auto LoggerBase::LogMessage(T&&... args) -> void
{
    if (Verbose)
    {
        constexpr int size{ 256 };
        char msgBuffer[size];
        auto size_copied = sprintf_s(msgBuffer, size, std::forward<T>(args)...);
        std::string msg(msgBuffer);
        LogMessageQuery.Add(msg);
    }
}

template<class ...T>
auto LoggerBase::LogMessageW(T&&... args) -> void
{
    if (Verbose)
    {
        constexpr int size{ 256 };
        wchar_t msgBuffer[size];
        auto size_copied = swprintf_s(msgBuffer, size, std::forward<T>(args)...);
        std::wstring wmsg(msgBuffer);
        LogMessageQuery.Add(wmsg);
    }
}

into:

template<class ...T>
auto LoggerBase::LogMessageAny(T&&... args) -> void
{
    if (Verbose)
    {
        using firstType = typename std::tuple_element<0, std::tuple<T...>>::type;
        constexpr int size{ 256 };
        if (typeid(firstType) == typeid(const wchar_t*))
        {
            wchar_t msgBuffer[size];
            auto size_copied = swprintf_s(msgBuffer, size, std::forward<T>(args)...);
            std::wstring wmsg(msgBuffer);
            LogMessageQuery.Add(wmsg);
        }
        else if (typeid(firstType) == typeid(const char*))
        {
            char msgBuffer[size];
            auto size_copied = sprintf_s(msgBuffer, size, std::forward<T>(args)...);
            std::string msg(msgBuffer);
            LogMessageQuery.Add(msg);
        }
    }
}

however those test won't compile:

typedef void(__stdcall* MessageChangedCallback)(const wchar_t* string);
static MessageChangedCallback log = [](const wchar_t* z)
    {
        Logger::WriteMessage(z);
    };

TEST_CLASS(LoggerBaseTests)
{
public:
    TEST_METHOD(LogMessageAny_stringTest)
    {
        LoggerBase sut(log);
        sut.LogMessageAny("This is a good std::string TEST %i!", 123);
    }

    TEST_METHOD(LogMessageAny_wstringTest)
    {
        LoggerBase sut(log);
        sut.LogMessageAny(L"This is a good std::wstring TEST %i!", 456);
    }

};

Severity Code Description Project File Line Suppression State Details Error C2664 'int swprintf_s(wchar_t *const ,const size_t,const wchar_t *const ,...)': cannot convert argument 3 from 'const char [36]' to 'const wchar_t *const ' UtilTests C:\Users\soleil\source\repos\SublimeTriptych\util\LoggerBase.h 78

Severity Code Description Project File Line Suppression State Details Error C2664 'int sprintf_s(char *const ,const size_t,const char *const ,...)': cannot convert argument 3 from 'const wchar_t [37]' to 'const char *const ' UtilTests C:\Users\soleil\source\repos\SublimeTriptych\util\LoggerBase.h 85

The inital two functions do work fine.

  1. Is my type switch proper ?

  2. How can I specialise the first arg and forward the rest ? ie., at the call of sprintf(): something in the mindset of swprintf_s(msgBuffer, size, (const char*)args[0], std::forward<T>(args[1..])...)

How to specializing a forwarding parameter pack?

do work with two parameters, but I need to keep a generic parameter pack for args of index >1, therefore it's not a solution.

  1. Should I specialise already in the parameters of the function LogMessage() and overload it instead ?

Solution

  • Since you always need a format string, you should use a separate template argument to deduce its type, and then you can use if constexpr to make decisions on that type at compile-time rather than at run-time, eg:

    #include <type_traits>
    
    template<class CharT, class ...T>
    void LoggerBase::LogMessageAny(const CharT *fmt, T&&... args)
    {
        static_assert(std::is_same_v<CharT, wchar_t> || std::is_same_v<CharT, char>);
    
        if (Verbose)
        {
            constexpr int size{ 256 };
            if constexpr (std::is_same_v<CharT, wchar_t>)
            {
                wchar_t msgBuffer[size];
                auto size_copied = swprintf_s(msgBuffer, size, fmt, std::forward<T>(args)...);
                std::wstring wmsg(msgBuffer, size_copied);
                LogMessageQuery.Add(wmsg);
            }
            else
            {
                char msgBuffer[size];
                auto size_copied = sprintf_s(msgBuffer, size, fmt, std::forward<T>(args)...);
                std::string msg(msgBuffer, size_copied);
                LogMessageQuery.Add(msg);
            }
        }
    }
    

    And then you can simplify the code further by taking advantage of the fact that std::string and std::wstring are just specializations of std::basic_string, eg:

    #include <type_traits>
    
    template<class CharT, class ...T>
    void LoggerBase::LogMessageAny(const CharT *fmt, T&&... args)
    {
        static_assert(std::is_same_v<CharT, wchar_t> || std::is_same_v<CharT, char>);
    
        if (Verbose)
        {
            constexpr int size{ 256 };
            CharT msgBuffer[size];
            std::basic_string<CharT>::size_type size_copied;
    
            if constexpr (std::is_same_v<CharT, wchar_t>)
            {
                size_copied = swprintf_s(msgBuffer, size, fmt, std::forward<T>(args)...);
            }
            else
            {
                size_copied = sprintf_s(msgBuffer, size, fmt, std::forward<T>(args)...);
            }
    
            std::basic_string<CharT> msg(msgBuffer, size_copied);
            LogMessageQuery.Add(msg);
        }
    }
    

    And then you can break out the ...printf functions into their own wrappers and get rid of if constexpr, eg:

    template<size_t MsgBufferSize, class ...T>
    int printf_t(wchar_t (&msgBuffer)[MsgBufferSize], const wchar_t* fmt, T&&... args)
    {
        return swprintf_s(msgBuffer, MsgBufferSize, fmt, std::forward<T>(args)...);
    }
    
    template<size_t MsgBufferSize, class ...T>
    int printf_t(char (&msgBuffer)[MsgBufferSize], const char* fmt, T&&... args)
    {
        return sprintf_s(msgBuffer, MsgBufferSize, fmt, std::forward<T>(args)...);
    }
    
    template<class CharT, class ...T>
    void LoggerBase::LogMessageAny(const CharT *fmt, T&&... args)
    {
        if (Verbose)
        {
            CharT msgBuffer[256];
            int size_copied = printf_t(msgBuffer, fmt, std::forward<T>(args)...);
            std::basic_string<CharT> msg(msgBuffer, size_copied);
            LogMessageQuery.Add(msg);
        }
    }