printfに型チェックを付ける

どうもコンパイラ拡張で文字列を型パラメータとして取れるユーザ定義リテラルがあるようなので,printfに型チェックを付けるようなのを書いてみた. 桁数表示とか何も実装してないけれど,雰囲気がそれっぽくなってきたあたりで飽きた.もっと賢い実装方法があるように思う.あと地味にrequiresなんてconceptカッコつけて使ってみたり.

melpon.org

#include <iostream>
#include <tuple>
#include <type_traits>
#include <utility>

namespace detail {

template <char C>
struct format {};

template <> struct format<'d'> { using type = int; };
template <> struct format<'c'> { using type = char; };
template <> struct format<'s'> { using type = const char*; };
template <> struct format<'f'> { using type = float; };
    
template <typename Str, typename F, typename B, char... Chars>
struct formatter {
    using type = std::tuple<Str, F>;
};

template <typename Str, typename... Fs, bool B, bool... Bs, char C, char... Chars>
requires B
struct formatter<Str, std::tuple<Fs...>, std::integer_sequence<bool, B, Bs...>, C, Chars...> {
    using type = typename formatter<
        Str,
        std::tuple<Fs..., typename format<C>::type>,
        std::integer_sequence<bool, Bs...>,
        Chars...>::type;
};
    
template <typename Str, typename... Fs, bool B, bool... Bs, char C, char... Chars>
requires not B
struct formatter<Str, std::tuple<Fs...>, std::integer_sequence<bool, B, Bs...>, C, Chars...> {
    using type = typename formatter<
        Str, std::tuple<Fs...>,
        std::integer_sequence<bool, Bs...>,
        Chars...>::type;
};

template <typename... Types>
struct and_ {
    static constexpr bool value = false;
};

template <typename... Types>
struct and_<Types*...> {
    static constexpr bool value = true;
};
    
}

template <bool... Bs>
struct and_ {
    static constexpr bool value = detail::and_<
        typename std::conditional<Bs, int*, int>::type...>::value;
};

template <char... Chars>
struct formatter : detail::formatter<
    std::integer_sequence<char, Chars...>,
    std::tuple<>,
    std::integer_sequence<bool, false, (Chars == '%')...>,
    Chars...,
    '\0'> {};

template <typename Char, Char... Chars>
constexpr auto operator ""_fmt() {
    return typename formatter<Chars...>::type{};
}

template <char... Chars, typename... Types, typename... UTypes>
void print(const std::tuple<std::integer_sequence<char, Chars...>,
           std::tuple<Types...>>&, UTypes&&... args) {
    static_assert(and_<std::is_convertible<std::decay_t<Types>, std::decay_t<UTypes&&>>::value...>::value);
    const char str[sizeof...(Chars)] = { Chars... };
    std::printf(str, std::forward<UTypes>(args)...);
}

int main() {
    print("%d %d %f %s\n"_fmt, 10, 20, 3.14f, "hoge");
}