I stumbled upon this code in the SerenityOS project:
template<typename... Parameters>
void dbgln(CheckedFormatString<Parameters...>&& fmtstr, const Parameters&... parameters)
They are re-implementing an equivalent of println! from rust. An easier version of printf where you don't need to care about the argument type and is used this way:
dbgln("This is a {}", "test");
They are doing some compile time checks on the fmtstr making sure that there are no unclosed braces and that the number of braces match the number of arguments. That's why the FormatString struct need to be templated to have access to the number of argument at compile time.
But there is something I don't understand. I wrote an MWE with the code below which, in essence, reproduce what they are doing:
#include <stddef.h>
template<typename... Args>
void compiletime_fail(Args...);
template<typename ...Parameters>
struct UnconstrainedFormatString {
template <size_t size>
consteval UnconstrainedFormatString(const char (&buffer)[size]): m_buffer(buffer), m_size(size) {
}
const char *m_buffer { nullptr };
const size_t m_size { 0 };
};
template<typename T>
struct __IdentityType {
using Type = T;
};
template<typename T>
using IdentityType = typename __IdentityType<T>::Type;
template<typename... Args>
using FormatString = UnconstrainedFormatString<IdentityType<Args>...>; // but why?
template<typename ...Parameters>
constexpr void println(FormatString<Parameters...>&& fmtstr, const Parameters& ...parameters) {
}
int main() {
println("this is a test", 1, 2, 3);
}
if I used UnconstrainedFormatString in the println signature I get this error from the compiler:
/cplayground/code.cpp:32:3: error: no matching function for call to 'println'
println("this is a test", 1, 2, 3);
^~~~~~~
/cplayground/code.cpp:28:16: note: candidate template ignored: could not match 'UnconstrainedFormatString<type-parameter-0-0...>' against 'char const[15]'
constexpr void println(UnconstrainedFormatString<Parameters...>&& fmtstr, const Parameters& ...parameters) {
In order for it to compile, I need to do that funky business with IdentityType.
Why do I need this?
See the https://en.cppreference.com/w/cpp/language/template_argument_deduction#Implicit_conversions :
Type deduction does not consider implicit conversions (other than type adjustments listed above): that's the job for overload resolution, which happens later.
How does the FormatString/IdentityType change things? The IdentityType suppresses the deduction of template parameters for const char[N] and now the string is used as a constructor parameter for UnconstrainedFormatString<...>.
You can learn more details here: https://humanreadablemag.com/issues/0/articles/how-to-avoid-template-type-deduction-in-c
When you use UnconstrainedFormatString only, the compiler tries to deduct UnconstrainedFormatString from const char [N] and fails since for templates it "doesn't know" that the conversion from const char[N] to UnconstrainedFormatString exists.
You can easily check with the
println(UnconstrainedFormatString<int, int, int>("test"), 1, 2, 3);
Since no conversion is needed here, it works, as well.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With