How to help the compiler deduce the arguments of a variadic function?
16:09 19 Nov 2025

I have been working on trying to convert some legacy software to use variadic templates. However, I have been really struggling on overhauling one of the classes, which provides logging, and keeping the changes super minimal.

For example:

#define ThisFile __FILE__ << ":" << __LINE__

#include "Log.hpp"

auto main() -> int
{
  auto Logger = Log{};
  const auto a = int{0};
  const auto b = int{1};
  Logger->print("%s:%d %d %d", ThisFile, a, b);
}

I have been trying really hard to keep that syntax identical, since this logger is used in hundreds of files and in hundreds of lines of code. I have been able to get std::source_location and the variadic templates to play nicely with each other using deduction guides.

#pragma once

#include 
#include 
#include 
#include 

struct Log
{
  template
  print(const char* format, Args&&...args, const std::source_location& loc)
  {
    const auto args_pack = std::tuple{ std::forward(args)...};
    const auto msg = std::apply([&format](auto&...args) {
      return std::vformat(format, std::make_format_args(args...));
    }, args_pack);

    std::println("{}:{} {}", loc.file_name(), loc.line(), msg);
  }

  template
  print(const std::string_view, Args&&...) -> printIt;
};

However, using this would require the syntax to be changed to something like Log::print Which would break a lot of legacy code. So I decided to wrap it in a function:

#pragma once

#include 
#include 
#include 
#include 

struct Log
{
  template
  printIt(const char* format, Args&&...args, const std::source_location& loc)
  {
    const auto args_pack = std::tuple{ std::forward(args)...};
    const auto msg = std::apply([&format](auto&...args) {
      return std::vformat(format, std::make_format_args(args...));
    }, args_pack);

    std::println("{}:{} {}", loc.file_name(), loc.line(), msg);
  }

  template
  printIt(const std::string_view, Args&&...) -> printIt;

  template
  static auto print(const std::string_view format, Args&...args,const std::source_location loc = std::source_location::current()) -> void
  {
    printIt(format, std::forward(args)..., loc);
  }
};

However, the issue with this is that I need to specify the template parameters where I am calling it:

#include "Log.hpp"

auto main() -> int
{
  auto Logger = Log{};
  const auto a = int{0};
  const auto b = int{1};
  Logger->print("{} {}", a, b);
}

Yes, I do realize that in this example I have removed %d and replaced them with {}. I have written code that will convert the C style syntax and format it into a string using snprintf. I also removed ThisFile since that will be replaced with std::source_location. What I want to achieve is this:

#include "Log.hpp"

auto main() -> int
{
  auto Logger = Log{};
  const auto a = int{0};
  const auto b = int{1};
  Logger->print("%d %d", a, b);
}

However, if I do that I do get a compiler error that says something like template argument deduction/substitution failed

Is there no way to achieve what I am trying to do without specifying the types that go into the template parameter? Or can I help the compiler by giving it hints as to what it should deduce to? I know that deduction guides cannot work on functions, or I would have tried to do that first, but I am stuck here and unsure of where to go next.

c++ templates variadic-templates