2

I am dealing with a library of which I cannot change the data types. I need to call a function that takes a bool array. The code that I am working with uses std::vector<bool> to store the bools. I have read a lot about std::vector<bool> and the associated problems on SO, but I have not found the most elegant solution for my problem. This is a code where performance is crucial. How do I deal with this problem in the most efficient way? I can change the type stored in the std::vector, but I cannot escape from using std::vector.

In my own problem, I have to call Fortran functions, but I have made a minimal example in C++ that illustrates the problem.

#include <cstdio>
#include <vector>

void print_c_bool(bool* bool_array, size_t n)
{
    for (int i=0; i<n; ++i)
        printf("%d, %d\n", i, bool_array[i]);
}

int main()
{
    // This works.
    bool bool_array[5] = {true, false, false, true, false};
    print_c_bool(bool_array, 5);

    // This is impossible, but how to solve it?
    std::vector<bool> bool_vector {true, false, false, true, false};
    // print_c_bool(bool_vector.data(), bool_vector.size());

    return 0;
}
Chiel
  • 6,006
  • 2
  • 32
  • 57
  • `std::vector` can be used to hold `0`s or `1`s, though i'd also love to see a better workaround – 463035818_is_not_an_ai May 06 '19 at 08:56
  • You have to construct an array of bools, that's the only way. – KamilCuk May 06 '19 at 08:58
  • What about this: `std::vector bool_vector{ 1, 0 }; print_c_bool((bool *)bool_vector.data(), bool_vector.size());`? Roughly. – vahancho May 06 '19 at 09:00
  • @vahancho No. It violates strict alias rule and will not work when `sizeof(char) != sizeof(bool)` and in other cases. – KamilCuk May 06 '19 at 09:02
  • 2
    I am afraid you'll have to accept the fact that `std::vector::data` is not an array of booleans, if you need one then you have to use something else than `std::vector` – 463035818_is_not_an_ai May 06 '19 at 09:02
  • @KamilCuk you are right. `sizeof(bool)` may not be equal to 1. I have deleted my answer as I assumed that `sizeof(bool)` is always 1. – taskinoor May 06 '19 at 09:04
  • If braking strict alias violation I would go with `class Bool { bool v; };` with at least `static_assert(sizeof(Bool) == sizeof(bool) && std::is_pod == true)` and then call `reinterpret_cast(std::vector::data())`. Looks as dirty as other casting solutions and seems to work on my system. – KamilCuk May 06 '19 at 09:20
  • ``It is really a pity that the standard has defined `std::vector` not to be a vector of `bool`!`` But it is a fact... My opinion is that you should define a custom container that behaves as a `vector` for the functions that your really need. Even with no special optimization, it could not be worse than repeatedly converting from a bitset (what a `std::vector – Serge Ballesta May 06 '19 at 09:55
  • if you have a std::vector why don't you pass that to the print function? if you need the print function also for bool*, you could still make it a template... – choosyg May 06 '19 at 11:13
  • @choosyg. I cannot do that, because I cannot modify those functions, because it is an external library. – Chiel May 06 '19 at 11:30

3 Answers3

5

You know what you have to do... Create a temporary array of bools and copy the values.

auto v = new bool[bool_vector.size()];
std::copy(bool_vector.begin(), bool_vector.end(), v);
print_c_bool(v, bool_vector.size());
delete[] v;

or

auto v = std::make_unique<bool>(bool_vector.size());
std::copy(bool_vector.begin(), bool_vector.end(), v.get());
print_c_bool(v.get(), bool_vector.size());
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
2

I can change the type stored in the std::vector, but I cannot escape from using std::vector.

In this case you are out of luck. std::vector<bool> does not store an array of booleans, neither does its data() return a pointer to an array of booleans (as all other std::vectors do for their respective data type).

You can play some tricks and use std::vector<uint8_t> or similar, though even if the size matches, a uint8_t* is not a bool* ! Given that the function cannot change, you can only avoid to violate strict aliasing by copying the data into a bool array.

If you do care about performance, then I'd suggest to consider not using std::vector<bool>. For example, do you really need dynamic size? If not use a std::array.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • I need a dynamic size, unfortunately. The `uint8_t` seems to work nicely, but is guaranteed to work everywhere? – Chiel May 06 '19 at 11:31
  • @Chiel it is never guaranteed to work! You missed the "a `uint8_t*` is not a `bool*`!" Passing a `uint8_t*` when a `bool*` is expected may appear to work, but afaik it violates [strict aliasing](https://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule). The strict aliasing rule is a tricky one, because something that naively seems to be fine in fact is not, and a tiny compiler optimization can completely screw your program – 463035818_is_not_an_ai May 06 '19 at 13:13
0

I tried to make it work using your example and the following (quite dirty) solution worked fine:

#include <iostream>
#include <vector>

void test_print(bool * arr, size_t s)
{
    for(unsigned int i = 0; i< s; ++i)
        std::cout << i << ": " << (arr[i]?"true\n":"false\n");
    std::cout << std::endl;
}

int main()
{
    bool b_arr[5] = {true, false, false, true, false};
    test_print(b_arr, 5);

    //std::vector <uint8_t> b_vec = {true, false, false, true, false}; // former proposal
    std::vector <char> b_vec = {true, false, false, true, false}; // new proposal
    test_print(reinterpret_cast<bool*>(b_vec.data()), b_vec.size());

    return 0;
}

The output I got is:

0: true
1: false
2: false
3: true
4: false

0: true
1: false
2: false
3: true
4: false

However, I don't know if it will behave the same on every system/platform. But if you don't intend to change your system/platform and if it does work on yours, I think it could do the job even if it is a quite dirty solution.

In fact, if we can guarantee that sizeof(bool) == sizeof(uint8_t) and assuming that true and false are respectively treated in memory as integers 1 and 0, this solution will work, but not otherwise.

I hope it can help.

EDIT: Replaced uint8_t by char type.


EDIT2:

Another solution that does not violate the strict aliasing rule is the one that Kamil Cuk mentioned that is to create a Bool wrapper and still check that sizeof(bool) == sizeof(Bool).

A possible implementation (just the baseline code) can be:

struct Bool
{
    bool v;

    Bool(bool bv=false) : v(bv)
    {}
};

And then you will be able to write:

std::vector<Bool> b_vec {true, false, false, true, false};
test_print(reinterpret_cast<bool*>(b_vec.data()), b_vec.size());

It worked for me :)

Fareanor
  • 5,900
  • 2
  • 11
  • 37
  • Interesting. Then the big open question is to which extent we can guarantee this. Any ideas on that? – Chiel May 06 '19 at 12:56
  • `uint8_t` (if defined of course) will always have the same size by definition. According to me, this question is more about the size of `bool`. But I don't really know in which cases it is 1 (8 bits) and in which cases it is not (it depends on the platform I think). – Fareanor May 06 '19 at 13:13
  • afaik `bool` and `uint8_t` are not compatible with respect to [strict aliasing](https://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule) and what appears to work is not guaranteed to work. – 463035818_is_not_an_ai May 06 '19 at 13:15
  • @user463035818 If we use `char` instead of `uint8_t`, we are not violated the strict aliasing rule anymore, and the size of `char` is guaranteed to be 1. The result is the same. Thanks for noticing my error, I didn't know that. – Fareanor May 06 '19 at 13:45
  • afaik the exception is for a `char*` aliasing a pointer of different type. It is not for any other type aliasing a `char*`. And it is the latter you use in your code. – 463035818_is_not_an_ai May 06 '19 at 13:54
  • you can find the relevant quotes from the standard [here](https://stackoverflow.com/a/7005988/4117728). In a nutshell: you can access a bool via a char pointer but you shall not access a char via a `bool*` – 463035818_is_not_an_ai May 06 '19 at 13:59
  • Oh you are right, my bad. @Chiel Without any compiler optimization enabled and if the platform does respect what I specified in my answer, it will work fine. This is the only way I see to avoid the copy. Hope it helps. – Fareanor May 06 '19 at 14:01
  • I edited my answer, this is the idea that @Kamil Cuk proposed (not mine) and seems to work properly. – Fareanor May 06 '19 at 15:07