How to prevent specialization of std::vector

2019-04-20 08:36发布

问题:

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?

回答1:

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.



回答2:

Use std::vector<char> instead.



回答3:

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.



回答4:

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...



回答5:

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?



回答6:

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.