3

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?

Luke Skywalker
  • 1,464
  • 3
  • 17
  • 35
  • Why do you need the UnconstrainedFormatString as a template? If you define it as a struct, everything will work fine with the UnconstrainedFormatString. Don't you overcomplicate things? – Damir Tenishev Aug 20 '21 at 19:13
  • 1
    I think the purpose is to inspect at compile time that the number of braces matches the number of arguments. – Guillaume Courrier Aug 20 '21 at 19:16
  • 2
    To prevent deduction from that argument. See https://stackoverflow.com/questions/17433082/use-one-argument-for-template-parameter-deduction?noredirect=1&lq=1 – ecatmur Aug 20 '21 at 19:54
  • @ecatmur I think you might be onto something here. However in my example, there is no possible ambiguity because it is impossible to deduce the type of the variadic parameters from juste the format string. Care to elaborate a little more, maybe in an answer? – Luke Skywalker Aug 20 '21 at 20:06
  • Ok so https://en.cppreference.com/w/cpp/language/template_argument_deduction#Non-deduced_contexts explains the IdentityType which is equivalent to std::type_identity. But its used is to avoid ambiguous type deduction which is not the case here. The error is different. Still need to understand where the error comes from to have a full picture. – Luke Skywalker Aug 21 '21 at 06:06
  • @LukeSkywalker, what makes you think that the error is different here? identity::type gives the compiler the hint about the nested type (name). The same role role plays the __IdentityType here. Why you see it different? – Damir Tenishev Aug 21 '21 at 13:51

1 Answers1

1

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.

Damir Tenishev
  • 1,275
  • 3
  • 14