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?

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
Patrick
  • 23,217
  • 12
  • 67
  • 130

7 Answers7

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.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • A `std::vector` is not even a _standard container_ – K-ballo Jan 17 '13 at 20:57
  • @KonradRudolph yeah, you don't want to override `bool`, but only `vector`. If only the committee had added a `bitvector` and let `vector` be a regular vector... – TemplateRex Jan 17 '13 at 21:12
  • @Patrick you're welcom, but did you just missclick and remove the acceptance? is there something that needs to be added to this answer? – TemplateRex Jan 18 '13 at 10:48
  • 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. – Patrick Jan 18 '13 at 10:49
  • @Patrick Did you try my answer in your code and look for how many casts it takes to get it to compile correctly? – TemplateRex Jan 18 '13 at 10:56
  • @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. – Patrick Jan 18 '13 at 12:29
  • @Patrick note that in C++11 you can make both the constructor and the conversion operator explicit. – TemplateRex Jan 18 '13 at 12:33
  • @rhalbersma, I added the explicit initialization of value_ to the default constructor to make sure it's always correctly initialized. – Patrick Jan 18 '13 at 12:46
4

Use std::vector<char> instead.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
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.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • Your answer gave me some inspiration. Can you take a look at my answer and tell me what you think of it? – Patrick Jan 18 '13 at 10:51
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.

Apples
  • 3,135
  • 1
  • 21
  • 26
  • Your answer gave me some inspiration. Can you take a look at my answer and tell me what you think of it? – Patrick Jan 18 '13 at 10:50
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?

Patrick
  • 23,217
  • 12
  • 67
  • 130
  • Wouldn't it be even shorter to write `template struct wrapper { T value_; };` without the constructors? – TemplateRex Jan 18 '13 at 10:55
  • BTW, you only need `static_cast`, not `reinterpret_cast` for my answer. – TemplateRex Jan 18 '13 at 10:57
  • Then there is no automatic casting from T to wrapperClass, 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. – Patrick Jan 18 '13 at 11:02
  • static_cast fails when casting references. You cannot statically-cast a char-reference to a bool-reference. – Patrick Jan 18 '13 at 11:04
  • 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`. – Konrad Rudolph Jan 18 '13 at 11:08
  • @Patrick +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` instead of `bool`). – TemplateRex Jan 18 '13 at 11:22
1
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.

Dwayne Robinson
  • 2,034
  • 1
  • 24
  • 39
-1

There is a ways to prevent the vector<bool> specialization: Passing a custom allocator.

std::vector<bool, myallocator> realbool; 

The following article has some details: https://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=98

Of course, this requires that you have control over the vector definitions, so it's probably not really a solution for you. Apart from that, it also has some downsides of it's own...

lethal-guitar
  • 4,438
  • 1
  • 20
  • 40