Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ constraints on variadic template arguments

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?

like image 204
Luke Skywalker Avatar asked Feb 24 '26 15:02

Luke Skywalker


1 Answers

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.

like image 186
Damir Tenishev Avatar answered Feb 26 '26 03:02

Damir Tenishev



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!