4

So I've been looking around and trying different things but I can't wrap my head around how I would go about creating some collection of strings with constexpr.

What I'm trying to do is basically the following, which obviously doesn't compile:

constexpr std::vector<std::string> fizzbuzz(){
    size_t N = 100;

    std::vector<std::string> result;
    result.reserve(N);

    for (int i = 0; i < N; i++){
        int k = i+1;

        if(k % 5 == 0 && k % 3 == 0){
            result.push_back("FizzBuzz");
        }
        else if(k % 5 == 0){
            result.push_back("Buzz");
        }
        else if(k % 3 == 0){
            result.push_back("Fizz");
        }
        else{
            result.push_back(std::to_string(k));
        }

    }
    return result;
}

I would already be happy if I understood how to do something as simple as:

constexpr std::string fizzbuzz(int k){
    if(k % 3 == 0) return "Fizz";
    else return std::to_string(k);
}

From there I reckon it's only a small step to the complete solution. It doesn't have to be std::strings it doesn't have to be std::vectors.

Oh, and the lower the C++Standard the better.

Edited to help understanding the problem better.

12
  • 4
    You can do this in c++20. Commented Sep 9, 2020 at 17:53
  • 4
    std::vector/std::string doesn't have constexpr constructor (before C++20)... std::array and std::string_view have. Commented Sep 9, 2020 at 17:53
  • 1
    @cigien: even in C++20, as I understand, OP won't be able to print (runtime) a constexpr container (which should stay in constexpr evaluation). Commented Sep 9, 2020 at 18:10
  • @Jarod42 Yes, that's correct. Commented Sep 9, 2020 at 18:12
  • 1
    oh, somehow I missed the point of the question, my bad Commented Sep 9, 2020 at 18:46

4 Answers 4

3

std::vector/std::string doesn't have constexpr constructor before C++20... and even in C++20, the constexpr allocation should not escape from constexpr evaluation, so cannot be used in runtime (for printing).

I don't see a standard constexpr way to transform an integer to a char sequence representation. std::to_string, std::to_chars, std::format are not constexpr.

So instead of homogeneous container, you might use std::tuple, something like (C++17):

template <std::size_t I>
constexpr auto fizzbuzz_elem()
{
    if constexpr (I % 5 == 0 && I % 3 == 0) {
        return "FizzBuzz";
    } else if constexpr (I % 5 == 0) {
        return "Buzz";
    } else if constexpr (I % 3 == 0){
        return "Fizz";
    } else {
        return I;
    }
}

template <std::size_t...Is>
constexpr auto fizzbuzz_impl(std::index_sequence<Is...>){
    return std::make_tuple(fizzbuzz_elem<1 + Is>()...);
}

template <std::size_t N>
constexpr auto fizzbuzz(){
    return fizzbuzz_impl(std::make_index_sequence<N>());
}

int main() {
    constexpr auto res = fizzbuzz<42>();
    std::apply([](auto... e){ ((std::cout << e << std::endl), ...); }, res);
}

Demo

Sign up to request clarification or add additional context in comments.

Comments

2

One way to do this kind of stuff is to use the frozen library, which works in C++14, and parts of it work in C++11. (We used it in production in some C++11 code.)

There are a couple things the library offers to make the constexpr happen:

While std::string can make calls ultimately to the dynamic memory allocator, which is not constexpr friendly (unless they made big advances in recent standards that I missed?) frozen::string is basically a string-span pointing to a string constant. So if your data structure is being constexpr initialized, frozen::string never makes an allocation, and that is why it can be constexpr friendly.

The frozen containers are meant to have API very similar to the C++ stdlib containers, but they are not modifiable after construction. Additionally, they are very efficient at run-time -- the maps are based on creating a perfect hash table, at compile time, and they don't make any dynamic memory allocations either.

Here's an example:

#include <frozen/unordered_map.h>
#include <frozen/string.h>

constexpr frozen::unordered_map<frozen::string, int, 2> olaf = {
    {"19", 19},
    {"31", 31},
};
constexpr auto val = olaf.at("19");

This can be very useful if you have a bunch of string constants that need to map to configuration values for your software, or vice-versa.

const variables at file-scope which are constexpr initialized do not have traditional static initialization since C++11. This means, their constructors are not called before main is entered, there is no "static initialization order fiasco". Instead they end up in the BSS read-only memory segment of the executable with the right values already in place. If you have many such maps or they are large, this can measurably improve the startup time of your application, due to not calling malloc and copying a lot of strings around before main is entered.

https://github.com/serge-sans-paille/frozen

4 Comments

Could you create the map programatically though? The main problem is probably the "int to string" part.
I think you can make the map programmatically if you can make the std::array of pairs that go in the map programmatically -- it won't work if you can't do that.
I remember that there is a cvector type which is a constexpr vector, on the stack, with a predetermined maximum capacity, that we used to implement the perfect hashing part. I can't remember if that's part of the public api though
0

std::vector and std::to_string() are not constexpr. Your second function example will work without those and while using std::string_view, like:

constexpr std::string_view fizzbuzz(const int k){ if(k % 3 == 0) return "Fizz"; else return "Buzz"; }

I believe std::string_view is c++17

1 Comment

Hey, thanks, but I don't want to return "Buzz" in the else but the to_string version of k
0

So with the help of @Jarod42 I solved it at last:

See comments for why this is striked through.

template <int N>
constexpr std::array<char[9], N> solution8(){
    std::array<char[9], N> result{};

    for(int i = 0; i < N; i++){
        int k = i + 1;
        if ((k % 3 == 0) && (k % 5 == 0)){
            sprintf(result[i], "FizzBuzz\0");
        }
        else if (k % 3 == 0){
            sprintf(result[i], "Fizz\0");
        }
        else if (k % 5 == 0){
            sprintf(result[i], "Buzz\0");
        }
        else {
            sprintf(result[i], "%d\0", i+1);
        }
    }

    return result;
}

4 Comments

I don't think sprintf is constexpr
@GuillaumeRacicot it does compile though, is that not proof enough?
No. Compilers have extension, implementation specific behavior and other things that can make code nonportable. In fact, none of the three major compiler is able to compile your code.
@GuillaumeRacicot Oh, thanks, I didn't add "constexpr" in the main. Removing that it compiles - I guess I should read more about constexpr. Thanks for the comment.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.