Не удаётся вывести тип в шаблоне

#include <source_location>
#include <iostream>
#include <string>
#include <string_view>
#include <format>

template<class... Args>
struct source_message
{
     consteval source_message(std::string_view formatString, const std::source_location& _location = std::source_location::current())
         : format{ formatString }
         , location{ _location }
     {

     }

    std::format_string<Args...> format;
    std::source_location location;
};

template<class... Args>
void log_trace_f(const source_message<Args...>& format, Args&&... args)
{
    std::cout << format.location.function_name() << " " << std::format(format.format, std::forward<Args>(args)...) << std::endl;
}


int main()
{
    // log_trace_f("{}", 123); // could not deduce template argument for 'const source_message<Args...> &' from 'const char [3]'
    log_trace_f({ "{}" }, 123); // int __cdecl main(void) 123
    
    return 0;
}

Есть ли способ завести первый вариант?

Фактически от функции log_trace_f требуется компайл тайм проверка строки форматирования и дополнительный вывод std::source_location в неявном виде, интерфейс функции log_trace_f должен быть похож на std::format()


Ответы (1 шт):

Автор решения: HolyBlackCat

Тут две проблемы.

Во-первых, когда первый аргумент не завернут в {...}, компилятор пытается вывести Args... не только из Args&&... args, но и также из const source_message<Args...>& format. И ругается, что не может это сделать.

Не буду притворяться, что понимаю, откуда такая разница в поведении (надо закапываться в стандарт), но фикс простой - первый параметр завернуть в type_identity, чтобы заблокировать вывод из него шаблонных аргументов: const std::type_identity_t<source_message<Args...>>& format.

После этого вылезает вторая ошибка:
no known conversion from 'const char[3]' to 'const std::type_identity_t<source_message<int>>' (aka 'const source_message<int>') for 1st argument

Компилятор не хочет вызывать больше одного пользовательского преобразования за раз (const char [3] -> std::string_view -> source_message<int>). Фикс - зашаблонить конструктор source_message, чтобы убрать второе преобразование:

template <std::convertible_to<std::string_view> T>
consteval source_message(T &&format, const std::source_location &location = std::source_location::current())
    : format(std::forward<T>(format)), location(location)
{}
→ Ссылка