Search code examples
c++templatesc++builderborland-c++

bcc32: strange error when specialising on `std::vector<bool>`


The classic 32-bit Borland/Embarcadero compiler - a.k.a. bcc32 - exhibits a strange failure when a traits class is specialised for std::vector<bool>. In particular, it fails to compile usages of the specialisation because it doesn't find any of its members. With other types - like std::vector<char> - there is no problem at all. Tested with BC++ 5.5.1 (free) and BC++ 7.1 (RX/Seattle).

Is there a workaround for this?

#include <iostream>
#include <typeinfo>
#include <vector>

template<typename T>
struct traits {  };

template<> struct traits< std::vector<char> >
{
   enum {  ENUM = 42  };

   static int func ()  {  return ENUM;  }
};

template<> struct traits< std::vector<bool> >
{
   enum {  ENUM = 666  };

   static int func ()  {  return ENUM;  }
};

///////////////////////////////////////////////////////////////////////////////////////////////////

template<typename T>
void test ()
{
   typedef traits<T> TT;

   // separate lines to see exactly where the compiler barfs
   std::cout << typeid(T).name();
   std::cout << " " << TT::ENUM;        // E2451 Undefined symbol 'ENUM'
   std::cout << " " << TT::func();      // E2451 Undefined symbol 'func'

   TT tt;
   std::cout << " " << tt.ENUM;         // E2316 'ENUM' is not a member of 'traits<std::_Bvector>'
   std::cout << " " << tt.func();       // E2316 'func' is not a member of 'traits<std::_Bvector>'
   std::cout << "\n";
}

int main ()
{
   test< std::vector<char> >();
   test< std::vector<bool> >();
   return 0;
}

Note: a somewhat hackish workaround that can be useful in certain circumstances is to code the specialisation for vector<bool> into the primary template (which would normally be left undefined); the specialisations on other types can then be done as usual, and the code works as expected even with bcc32.

A runtime assert can verify that the only unspecialised incarnation of the traits template is the one for std::vector<bool>. Templates that use the traits would then invoke the assertion code in a convenient place (which could also be a static function).

template<typename T>
struct traits
{
   // specialisation for std::vector<bool> coded here...
   enum {  ENUM = 666  };

   static int func ()  {  return ENUM;  }

   static void assert_only_vector_bool_not_specialised ()
   {
      assert(typeid(T) == typeid(std::vector<bool>));
   }
};

struct traits_specialisation_base
{
   static void assert_only_vector_bool_not_specialised ()
   {
   }
};

template<> struct traits< std::vector<char> >: traits_specialisation_base
{
   enum {  ENUM = 42  };

   static int func ()  {  return ENUM;  }
};

// ...

template<typename T>
struct UsingTraits
{
   typedef traits<T> TT;

   UsingTraits ()
   {
      TT::assert_only_vector_bool_not_specialised();
   }
};

// ...

UsingTraits< std::vector<char> > erna;
UsingTraits< std::vector<bool> > fred;

Solution

  • There is something fishy in the std:: with std::vector<bool> so you need to use the std:: type instead. just change to:

    #include <iostream>
    #include <typeinfo>
    #include <vector>
    //---------------------------------------------------------------------------
    template<typename T> struct traits
        {
        // this is safe constructor/destructor for Borland BDS2006 and later
        traits(){};
        traits(traits& a){};
        ~traits(){};
        traits* operator = (const traits *a){};
        //traits* operator = (const traits &a){}; // use this only if you have dynamic allocation members
        };
    
    template<> struct traits< std::vector<char> >
        {
        enum {  ENUM = 42  };
        static int func ()  {  return ENUM;  }
        };
    
    template<> struct traits< std::_Bvector >   // here use the std type directly
        {
        enum {  ENUM = 666  };
        static int func ()  {  return ENUM;  }
        };
    //---------------------------------------------------------------------------
    template<typename T> void test ()
        {
        typedef traits<T> TT;
    
        // separate lines to see exactly where the compiler barfs
        std::cout << typeid(T).name();
        std::cout << " " << TT::ENUM;        // E2451 Undefined symbol 'ENUM'
        std::cout << " " << TT::func();      // E2451 Undefined symbol 'func'
    
        TT tt;
    
        std::cout << " " << tt.ENUM;         // E2316 'ENUM' is not a member of 'traits<std::_Bvector>'
        std::cout << " " << tt.func();       // E2316 'func' is not a member of 'traits<std::_Bvector>'
        std::cout << "\n";
    
        // can ignore this ... it is just output to memo I do not use console
        AnsiString s="";
        s=s+typeid(T).name() + "\n";
        s=s+" " + AnsiString( TT::ENUM ) + "\r\n";        // E2451 Undefined symbol 'ENUM'
        s=s+" " + AnsiString( TT::func() ) + "\r\n";      // E2451 Undefined symbol 'func'
        s=s+" " + AnsiString( tt.ENUM ) + "\r\n";         // E2316 'ENUM' is not a member of 'traits<std::_Bvector>'
        s=s+" " + AnsiString( tt.func() ) + "\r\n";       // E2316 'func' is not a member of 'traits<std::_Bvector>'
        Form1->mm_log->Lines->Add(s);
        }
    //---------------------------------------------------------------------------
    // this is your main()
    __fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
        {
        test< std::vector<char> >();
        test< std::vector<bool> >();
        }
    //---------------------------------------------------------------------------
    

    I use windows form app so ignore form stuff. The constructors/destructors are not necessary for compilation but you should add them because of the Borland BDS2006 and latter C++ engine bug. For more info see:

    The code above gives me this output:

    std::vector<char,std::allocator<char> >
     42
     42
     42
     42
    
    std::vector<std::allocator<bool> >
     666
     666
     666
     666