How to prevent specialization of std::vector<bool>
Asked Answered
D

6

16

I have a templated class that has a data member of type std::vector<T>, where T is also a parameter of my templated class.

In my template class I have quite some logic that does this:

T &value = m_vector[index];

This doesn't seem to compile when T is a boolean, because the [] operator of std::vector does not return a bool-reference, but a different type.

Some alternatives (although I don't like any of them):

  • tell my users that they must not use bool as template parameter
  • have a specialization of my class for bool (but this requires some code duplication)

Isn't there a way to tell std::vector not to specialize for bool?

Dendriform answered 17/1, 2013 at 17:37 Comment(4)
What is the exact error?Urbana
Could you just replace T& with typename std::vector<T>::reference_type?Downstream
Can you maybe specialize just parts of your code for std::vector<bool>, too? Kind of an adapter template, that is the same for everything except bool. But for all I know, std::vector<bool> is quite an oddball anyway, and breaks a lot of stuff.Urbana
boost::container::vector doesn't have the bool specialization. That could be used instead of the std one.Ambiguous
M
10

You simply cannot have templated code behave regularly for T equal to bool if your data is represented by std::vector<bool> because this is not a container. As pointed out by @Mark Ransom, you could use std::vector<char> instead, e.g. through a trait like this

template<typename T> struct vector_trait { typedef std::vector<T> type; };
template<> struct vector_trait<bool> { typedef std::vector<char> type; };

and then use typename vector_trait<T>::type wherever you currently use std::vector<T>. The disadvantage here is that you need to use casts to convert from char to bool.

An alternative as suggested in your own answer is to write a wrapper with implicit conversion and constructor

template<typename T>
class wrapper
{
public:
        wrapper() : value_(T()) {}
        /* explicit */ wrapper(T const& t): value_(t) {}
        /* explicit */ operator T() { return value_; }
private:
        T value_;
};

and use std::vector< wrapper<bool> > everywhere without ever having to cast. However, there are also disadvantages to this because standard conversion sequences containing real bool parameters behave differently than the user-defined conversions with wrapper<bool> (the compiler can at most use 1 user-defined conversion, and as many standard conversions as necessary). This means that template code with function overloading can subtly break. You could uncomment the explicit keywords in the code above but that introduces the verbosity again.

Martinelli answered 17/1, 2013 at 20:54 Comment(8)
A std::vector<bool> is not even a standard containerReprisal
@KonradRudolph yeah, you don't want to override bool, but only vector<bool>. If only the committee had added a bitvector and let vector<bool> be a regular vector...Martinelli
@Dendriform you're welcom, but did you just missclick and remove the acceptance? is there something that needs to be added to this answer?Martinelli
I removed the acceptance because I invented an even more elegant solution (based on your input and the input of the other answers). Please look at my answer and tell me what you think of it.Dendriform
@Dendriform Did you try my answer in your code and look for how many casts it takes to get it to compile correctly?Martinelli
@rhalbersma: In my case, it would require 2 casts, which is reasonable, although I don't like the reinterpret_cast from char& to bool&. I decide to go for the wrapper implementation, but with an explicit constructor and no conversion operator to prevent unwanted conversions. Thanks for pointing that out.Dendriform
@Dendriform note that in C++11 you can make both the constructor and the conversion operator explicit.Martinelli
@rhalbersma, I added the explicit initialization of value_ to the default constructor to make sure it's always correctly initialized.Dendriform
D
4

Use std::vector<char> instead.

Dale answered 17/1, 2013 at 20:58 Comment(0)
O
4

Would the following work for you?

template <typename T>
struct anything_but_bool {
    typedef T type;
};

template <>
struct anything_but_bool<bool> {
    typedef char type;
};

template <typename T>
class your_class {
    std::vector<typename anything_but_bool<T>::type> member;
};

Less flippantly, the name anything_but_bool should probably be prevent_bool or similar.

Ormuz answered 17/1, 2013 at 21:5 Comment(1)
Your answer gave me some inspiration. Can you take a look at my answer and tell me what you think of it?Dendriform
E
2
std::basic_string<bool> flags(flagCount, false);

Semantically, using string is weird, but it works basically like std::vector, behaves as expected with pointers/references, and it conveniently converts to std::span<bool> when passing to functions that take span, no need for wrapper classes or reinterpret_cast's (e.g. using std::vector<uint8_t> makes these cases awkward because of the casts). Until/unless C++ adds a std::bit_vector and deprecates the specialization, we don't have many good options.

Erv answered 10/2, 2023 at 10:23 Comment(0)
P
1

You could use a custom proxy class to hold the bools.

class Bool
{
  public:
    Bool() = default;
    Bool(bool in) : value(in) {}

    Bool& operator=(bool in) {value = in;}
    operator bool() const& {return value;}

  private:
    bool value;
};

This might require a bit of tweaking for your purposes, but it's usually what I do in these cases.

Plough answered 17/1, 2013 at 21:7 Comment(1)
Your answer gave me some inspiration. Can you take a look at my answer and tell me what you think of it?Dendriform
D
1

I found an even more elegant solution, based on all of your input.

First I define a simple class that holds one member. Let's call this wrapperClass:

template <typename T>
class wrapperClass
   {
   public:
      wrapperClass() {}
      wrapperClass(const T&value) : m_value(value) {}
      T m_value;
   };

Now I can define my std::vector in my templated class like this:

std::vector<wrapperClass<T>> m_internalVector;

Since sizeof(WrapperClass<bool>) is also 1, I expect that sizeof(WrapperClass<T>) will always be equal to sizeof(T). Since the data type is now not a bool anymore, the specialization is not performed.

In places where I now get an element from the vector, I simply replace

m_internalVector[index]

by

m_internalVector[index].m_value

But this seems much more elegant than using traits to replace bool by char, and then using casts to convert between char and bool (and probably reinterpret casts to convert char& to bool&).

What do you think?

Dendriform answered 18/1, 2013 at 10:48 Comment(6)
Wouldn't it be even shorter to write template<typename T> struct wrapper { T value_; }; without the constructors?Martinelli
BTW, you only need static_cast, not reinterpret_cast for my answer.Martinelli
Then there is no automatic casting from T to wrapperClass<T>, and no default constructor, which leaves the value in the struct undefined. In my case, I need to do a resize of the vector, and so I need the default constructor.Dendriform
static_cast fails when casting references. You cannot statically-cast a char-reference to a bool-reference.Dendriform
If at all, I would only wrap bool and not the other types (using traits) and then provide implicit conversions so that you don’t have to write foo[index].m_value but can use foo[index] as if it were a proper bool.Ormuz
@Dendriform +1 for the idea, but as @KonradRudolph suggests, add an implicit conversion operator as well. And see my updated answer for the downside of implicit conversions (could break function overloading if you use wrapper<bool> instead of bool).Martinelli

© 2022 - 2024 — McMap. All rights reserved.