Share via

Using variadic parameters in MFC / C++20 application

ChuckieAJ 391 Reputation points
2026-06-07T17:58:31.1766667+00:00

I have a MFC application, but it is configured to build with the C++ 20 standard. Where useful I try to use the newer constructs.

Today, I decided to try and implement some variadic functions. This is what I have so far:


    void CBaseCsvImporter::LogInfo(const CString& msg)
    {
        m_dlgLog.AddLogEntry(msg, true);
    }

    void CBaseCsvImporter::LogSkip(const CString& msg)
    {
        m_dlgLog.AddLogEntry(msg, true);
        ++m_skippedCount;
    }

    void CBaseCsvImporter::LogError(const CString& msg)
    {
        m_dlgLog.AddLogEntry(msg, true);
        m_dlgLog.SetErrorMode(true);
        ++m_errorCount;
    }

    void CBaseCsvImporter::LogInfo(const wchar_t* fmt, ...)
    {
        CString msg;
        va_list args;
        va_start(args, fmt);
        msg.FormatV(fmt, args);
        va_end(args);

        LogInfo(msg); // calls the existing CString overload
    }

    void CBaseCsvImporter::LogSkip(const wchar_t* fmt, ...)
    {
        CString msg;
        va_list args;
        va_start(args, fmt);
        msg.FormatV(fmt, args);
        va_end(args);

        LogSkip(msg);
    }

    void CBaseCsvImporter::LogError(const wchar_t* fmt, ...)
    {
        CString msg;
        va_list args;
        va_start(args, fmt);
        msg.FormatV(fmt, args);
        va_end(args);

        LogError(msg);
    }

Understandably the Visual Studio IDE started showing code analysis warnings, discouraging me from using this approach:

Snapshot of code analysis

I decided to ask Copilot / ChatGPT about this and it prosed using these functions in the header instead:

template<typename... Args>
void LogInfo(std::wstring_view fmt, Args&&... args)
{
    std::wstring msg = std::vformat(fmt, std::make_wformat_args(args...));
    LogInfo(CString(msg.c_str()));
}

AI says that I would need to adjust my code to call it like this:

LogSkip(L"Skipped line {}: duplicate publisher '{}'", csvLine, name);

Eventually the error messages will come from the string table as a resource. I would appreciate guidance on the best way forward to make use of the new way of doing the variadic functiosn, but keeping in mind that ultimately I use CString.

Windows development | Windows API - Win32
0 comments No comments

Answer accepted by question author

Viorel 127K Reputation points
2026-06-07T18:57:48.01+00:00

Which errors do you mean?

In order to make this work:

CString s1 = L"text1";
const wchar_t* s2 = L"text2";
std::wstring s3 = L"text3";

LogInfo( L"Info {}, {}, {}", s1, s2, s3 );

this class can be added to header file (according to https://en.cppreference.com/cpp/utility/format/formatter):

template< >
struct std::formatter<CString, wchar_t> 
{
    template<class ParseContext>
    constexpr ParseContext::iterator parse( ParseContext& ctx )
    {
        return ctx.end( );
    }

    template<class FmtContext>
    FmtContext::iterator format( CString s, FmtContext& ctx ) const
    {
        std::wostringstream out;

		out << LPCWSTR(s);

        return std::ranges::copy( std::move( out ).str( ), ctx.out( ) ).out;
    }
};

Also add:

#include <string>
#include <string_view>
#include <sstream>
#include <format>

Was this answer helpful?

1 person found this answer helpful.

1 additional answer

Sort by: Most helpful
  1. Taki Ly (WICLOUD CORPORATION) 2,225 Reputation points Microsoft External Staff Moderator
    2026-06-08T03:36:31.35+00:00

    Hello @ChuckieAJ ,

    Since you mentioned needing to work with CString and eventually pulling format strings from the String Table resource, you might want to consider the following approach to see if it fits your project's architecture.

    1. Extending std::format for CString By default, std::format does not natively support MFC's CString. One potential workaround is to provide a custom std::formatter specialization. You could inherit from std::formatter<std::wstring_view, wchar_t>, which is often a relatively efficient way to parse and format the string without causing unnecessary string copies.

    Documentation reference: std::formatter (cppreference)

    #include <format>
    #include <string_view>
    // This could be placed in your pch.h or a common header
    template <>
    struct std::formatter<CString, wchar_t> : std::formatter<std::wstring_view, wchar_t> 
    {
        template <class FormatContext>
        auto format(const CString& str, FormatContext& ctx) const 
        {
            return std::formatter<std::wstring_view, wchar_t>::format(
                std::wstring_view(str.GetString(), str.GetLength()), ctx);
        }
    };
    

    2. Template overloads in the header To handle both hardcoded strings and Resource IDs, you could define a couple of template overloads directly in your CBaseCsvImporter.h. Using std::vformat inside the template might be a convenient way to format the arguments at runtime.

    Documentation reference: std::vformat (cppreference)

    class CBaseCsvImporter
    {
    public:
        // 1. The base function (typically kept in your .cpp file)
        void LogInfo(const CString& msg);
        // 2. A suggested overload for hardcoded string literals
        template<typename... Args>
        void LogInfo(std::wstring_view fmt, Args&&... args)
        {
            try {
                std::wstring msg = std::vformat(fmt, std::make_wformat_args(args...));
                LogInfo(CString(msg.c_str()));
            }
            catch (const std::format_error& e) {
                // std::vformat may throw if arguments mismatch
                CString errMsg;
                errMsg.Format(L"[Format Error]: %S", e.what());
                LogInfo(errMsg);
            }
        }
        // 3. A suggested overload for String Table Resource IDs
        template<typename... Args>
        void LogInfo(UINT nResourceID, Args&&... args)
        {
            CString resString;
            if (resString.LoadString(nResourceID))
            {
                // Extract wstring_view and forward it
                LogInfo(std::wstring_view(resString.GetString(), resString.GetLength()), std::forward<Args>(args)...);
            }
            else
            {
                LogInfo(CString(L"[ERROR: Missing Resource ID]"));
            }
        }
    };
    

    3. Example usage If you decide to implement the above templates, you would typically be able to use your logging functions like this:

    CString publisher = L"Example Corp";
    int csvLine = 42;
    // With a hardcoded string:
    LogInfo(L"Skipped line {}: duplicate publisher '{}'", csvLine, publisher);
    // With a String Table Resource ID:
    LogInfo(IDS_LOG_SKIP_DUPLICATE, csvLine, publisher);
    

    This setup might be a viable path to bridge modern C++20 features with traditional MFC types, while also preparing your application for localization. You can review this pattern to see if it aligns with your codebase.

    Hope this gives you some useful ideas! If you found my response helpful or informative, I would greatly appreciate it if you could follow this guide for your confirmation.

    Thank you.

    Was this answer helpful?


Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.