4

We talked about the “fizz buzz” programming test today, I thought about implementing this with in C++ but with meta-programming. Ideally it would generate the output already during compilation time.

My current code uses templates, but it still has to be executed in order to produce the output. See it on Ideone.

I still use std::cout << in order to print the output on the screen. The FizzBuzz<i>::value will give either i, -1 (Fizz), -2 (Buzz), or -3 (FizzBuzz). Then word<value> is defined for the negative numbers and gives a char const *:

template <int i>
struct Print {
    static void print() {
        Print<i - 1>::print();
        auto const value = FizzBuzz<i>::value;
        if (value < 0) {
            std::cout << word<value>() << std::endl;
        } else {
            std::cout << value << std::endl;
        }
    }
};

The loop is gone due to recursion, there is a Print<0> which stops it.

Is there some way to print out word<value>() or value, which are known at compile time during compilation? This will probably not work with every compiler, so I am happy with a solution that works with GCC and/or Clang.

Martin Ueding
  • 8,245
  • 6
  • 46
  • 92
  • 1
    You cannot print at compile time in a platform independent way (yet) – AndyG Aug 24 '17 at 18:05
  • However, you may be interested in https://github.com/saarraz/static-print – AndyG Aug 24 '17 at 18:06
  • 2
    The point of compile time evaluation is not to output the result at compile time. It is to generate the result at compile time so that using (for example; outputting) it at runtime will be using a constant. But, I guess, if you *really* want to, you could probably write some convoluted template magic that would print the result at compile time as part of a compiler error message - pretty silly though.. – Jesper Juhl Aug 24 '17 at 18:08
  • Well, if you could build a string you could fire a `static_assert` at the end of the code and have it display the string as an error. – NathanOliver Aug 24 '17 at 18:11
  • 4
    A conforming compiler is not required to emit any diagnostics for a well-formed program, so there's no portable way to answer your question with a well-formed program. The best you could do is create an error message that contains your result (e.g. as a template parameter of an erroneous construction or an array size, or maybe as a static assertion). Of course any diagnostic for ill-formed programs is also unspecified, so your compiler might just say "boo". – Kerrek SB Aug 24 '17 at 18:12
  • How would you handle the data input during compilation time? – Thomas Matthews Aug 24 '17 at 19:06

1 Answers1

6

While making the string at compilation time is possible with some template trickery, printing it at compilation time doesn't seem reasonable.

Here's the code that just makes the string:

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

// Compile time string
template <char ...C> struct str_lit
{
    static constexpr char value[] {C..., '\0'};
};

// Integer to str_lit
constexpr std::size_t cexpr_pow(std::size_t x, std::size_t y)
{
    std::size_t ret = 1;
    while (y--)
        ret *= x;
    return ret;
}
template <std::size_t N, std::size_t X, typename = std::make_index_sequence<X>> struct num_to_sl_impl;
template <std::size_t N, std::size_t X, std::size_t ...Seq> struct num_to_sl_impl<N, X, std::index_sequence<Seq...>>
{
    static constexpr auto func()
    {
        if constexpr (N >= cexpr_pow(10,X))
            return num_to_sl_impl<N, X+1>::func();
        else
            return str_lit<(N / cexpr_pow(10,X-1-Seq) % 10 + '0')...>{};
    }
};
template <std::size_t N> using num_to_sl = decltype(num_to_sl_impl<N,1>::func());

// str_lit concatenation
template <typename F, typename ...P> struct sl_cat_impl {using type = typename sl_cat_impl<F, typename sl_cat_impl<P...>::type>::type;};
template <char ...C1, char ...C2> struct sl_cat_impl<str_lit<C1...>,str_lit<C2...>> {using type = str_lit<C1..., C2...>;};
template <typename F, typename ...P> using sl_cat = typename sl_cat_impl<F, P...>::type;

// The FizzBuzz
template <std::size_t N> struct fizzbuzz_impl
{
    using fizz = std::conditional_t<N % 3 == 0,
                                    str_lit<'f','i','z','z'>,
                                    str_lit<>>;
    using buzz = std::conditional_t<N % 5 == 0,
                                    str_lit<'b','u','z','z'>,
                                    str_lit<>>;
    using type = sl_cat<typename fizzbuzz_impl<N-1>::type,
                        std::conditional_t<N % 3 == 0 || N % 5 == 0,
                                           sl_cat<fizz, buzz>,
                                           num_to_sl<N>>,
                        str_lit<'\n'>>;
};
template <> struct fizzbuzz_impl<0>
{
    using type = str_lit<>;
};
template <std::size_t N> using fizzbuzz = typename fizzbuzz_impl<N>::type;

Example usage:

int main()
{
    std::cout << fizzbuzz<15>::value;
}

Output:

1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizzbuzz

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207