#include <concepts>
#include <format>
#include <string_view>
#include <type_traits>
template <typename ... Args>
struct FmtString {
std::format_string<Args ...> text;
template <typename StringCompatible>
requires (std::convertible_to<StringCompatible, std::string_view>)
consteval FmtString(StringCompatible const& description)
: text(description)
{}
};
template <typename ... Args>
void fails(FmtString<Args ...> const&, Args&& ...) noexcept {}
template <typename... Args>
using FmtStringArgIdentity = FmtString<std::type_identity_t<Args> ...>;
template <typename ... Args>
void works(FmtStringArgIdentity<Args ...> const&, Args&& ...) noexcept {}
int main() {
works("test {}", 42);
fails("test {}", 42);
}
GCC 错误消息:
<source>: In function 'int main()':
<source>:28:10: error: no matching function for call to 'fails(const char [8], int)'
28 | fails("test {}", 42);
| ~~~~~^~~~~~~~~~~~~~~
<source>:18:6: note: candidate: 'template<class ... Args> void fails(const FmtString<Args ...>&, Args&& ...)'
18 | void fails(FmtString<Args ...> const&, Args&& ...) noexcept {}
| ^~~~~
<source>:18:6: note: template argument deduction/substitution failed:
<source>:28:10: note: mismatched types 'const FmtString<Args ...>' and 'const char [8]'
28 | fails("test {}", 42);
| ~~~~~^~~~~~~~~~~~~~~
Compiler returned: 1
查看 libfmt 的源代码,我能够使我的代码工作。不幸的是,我不明白为什么这样做有效,也不明白为什么模板参数推导失败。
fails
调用有什么问题?为什么 works
调用可以解决这个问题?
具有相同效果的更简单示例:
#include <type_traits>
template <typename T>
struct foo {
template <typename U>
foo(U u) {}
};
template <typename T>
void fails(foo<T>,T) {}
template <typename T>
void works(std::type_identity_t<foo<T>>,T) {}
int main()
{
fails(12.0,42); // error
works(12.0,42);
}
您无法从
int
推断出 12.0
,从而将其转换为 foo<int>
。用作构造函数模板参数的 double
和用于实例化 int
的 foo<int>
之间根本没有任何关系。任何 foo<T>
都有一个从 double
到 foo<T>
的转换构造函数。
std::type_identity
是非推导的上下文。对于 works<T>
,T
仅从 42
推导出来,并且 works<int>
被实例化以获得 works<int>(foo<int>,int)
。现在,隐式转换开始生效,并且 12.0
可以通过转换构造函数转换为 foo<int>
。