Home > Mobile >  From one argument make two - at compile time
From one argument make two - at compile time

Time:08-12

I'm writing a special print function that produces a cstdio printf - statement at compile time. The idea is basically that you invoke a function special_print() with a variadic parameter list and the function will assemble the necessary printf - statement at compile time. Here's what I've got:

#include <cstdio>
#include <string_view>

// how to implement "expand" from below?

template <typename... Ts>
constexpr const char* cxpr_format = /* ... this one I've already figured out */

template <typename... Ts>
void special_print(Ts... arg)
{
    printf(cxpr_format<Ts...>, expand(arg)...);

    // should expand to printf("%.*s%d", string_view.size(), string_view.data(), int) with below function call
}

int main()
{
    std::string_view strview = "hello world";
    int integer = 2;

    special_print(strview, integer);
}

I've already figured out the part for creating my format string - that one is out of the question. But now comes the problem: In case when I've got a string_view passed in as an argument, I would need to conditionally expand it to two arguments to printf: one for the initial char pointer and one for the size which goes together with the %.*s format specifier. And this is the real crux. Can this be done somehow in C (at compile time)?

Note: Before any further questions arise I would like to point out that I have no idea on how to do this in C and this is my question. Every approach will be gladly taken into account!

CodePudding user response:

My initial thought is to forward each arg through a tuple, and then "varags apply" in the call.

template <typename T>
auto expand(T&& t) { return std::forward_as_tuple<T>(t); }

auto expand(std::string_view s) { return std::tuple<int, const char *>(s.size(), s.data()); }

template <typename... Ts>
void special_print(Ts... arg)
{
    using expanded = decltype(std::tuple_cat(expand(arg)...));

    []<std::size_t... Is>(auto tup, std::index_sequence<Is...>)
    {
        printf(cxpr_format<Ts...>, std::forward<std::tuple_element_t<Is, expanded>>(std::get<Is>(tup))...);
    }(std::tuple_cat(expand(arg)...), std::make_index_sequence<std::tuple_size_v<expanded>>{});

    // should expand to printf("%.*s%d", string_view.size(), string_view.data(), int) with below function call
}

See it on coliru

CodePudding user response:

Rather than use printf, which only knows about types shared with C, you can use the std::format library.

struct putchar_iterator {
    using iterator_category = std::output_iterator_tag;
    using value_type = void;
    using difference_type = std::ptrdiff_t;
    using pointer = void;
    using reference = void;

    putchar_iterator& operator=(char c) { 
        eof = (putchar(c) == EOF);
        return *this; 
    }
    constexpr putchar_iterator& operator*() { return *this; }
    constexpr putchar_iterator& operator  () { return *this; }
    constexpr putchar_iterator& operator  (int) { return *this; }

    friend bool operator==(putchar_iterator lhs, std::default_sentinel_t) { return lhs.eof; }
private:
    bool eof = false;
};

template <typename... Ts>
void special_print(Ts... arg)
{
    // You might be able to std::format_to, but I'm not sure
    std::vformat_to(putchar_iterator{}, cxpr_format<Ts...>, std::make_format_args(arg...));
}

See it on godbolt

  • Related