Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Member array with size defined by template parameter, but why no warning for zero-size array?

I was trying to write a templated base class to store a fixed number of data types, each with varying length. Here is a simplified version of much what I was trying to do:

template< int NINT, int NR0 >
class EncapsulatedObjectBase
{
   public:

  EncapsulatedObjectBase();

  ~EncapsulatedObjectBase();

  double m_real[NR0];
  int m_int[NINT];
}

Yeah...so the template parameters can be zero, thus declaring a zero-length array of objects. There will be multiple derived classes for this base, each defining their own number of variables. I have two questions:

1) Is this approach fundamentally flawed?

2) If so...why doesn't icc13 or gcc4.7.2 give me warnings about this when I instantiate a zero-length array? For gcc I use -wall and -wextra -wabi. The lack of warnings made me think that this sort of thing was OK.

EDIT:

Here is the contents of a file that show what I am talking about:

#include <iostream>

template< int NINT, int NR0 >
class EncapsulatedObjectBase
{
public:
  EncapsulatedObjectBase(){}
  ~EncapsulatedObjectBase(){}

  double m_real[NR0];
  int m_int[NINT];
};


class DerivedDataObject1 : public EncapsulatedObjectBase<2,0>
{
   public:

   DerivedDataObject1(){}

  ~DerivedDataObject1(){}

  inline int& intvar1() { return this->m_int[0]; }
  inline int& intvar2() { return this->m_int[1]; }

};


class DerivedDataObject2 : public EncapsulatedObjectBase<0,2>
{
   public:

   DerivedDataObject2(){}

  ~DerivedDataObject2(){}

  inline double& realvar1() { return this->m_real[0]; }
  inline double& realvar2() { return this->m_real[1]; }
};




int main()
{
   DerivedDataObject1 obj1;
   DerivedDataObject2 obj2;

   obj1.intvar1() = 12;
   obj1.intvar2() = 5;

   obj2.realvar1() = 1.0e5;
   obj2.realvar2() = 1.0e6;

   std::cout<<"obj1.intvar1()  = "<<obj1.intvar1()<<std::endl;
   std::cout<<"obj1.intvar2()  = "<<obj1.intvar2()<<std::endl;
   std::cout<<"obj2.realvar1() = "<<obj2.realvar1()<<std::endl;
   std::cout<<"obj2.realvar2() = "<<obj2.realvar2()<<std::endl;


}

If I compile this with "g++ -Wall -Wextra -Wabi main.cpp" I get no warnings. I have to use the -pedantic flag to get warnings. So I still don't know how unsafe this is. In retrospect, I feel as though it must not be a very good idea...although it would be pretty useful if I could get away with it.

like image 412
doc07b5 Avatar asked Jan 18 '13 00:01

doc07b5


People also ask

Can you declare an array with size 0?

Zero-length array declarations are not allowed, even though some compilers offer them as extensions (typically as a pre-C99 implementation of flexible array members).

What is the purpose of template parameter?

A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.

What does template <> mean in CPP?

Templates in c++ is defined as a blueprint or formula for creating a generic class or a function. To simply put, you can create a single function or single class to work with different data types using templates. C++ template is also known as generic functions or classes which is a very powerful feature in C++.

Can a template be a template parameter?

Templates can be template parameters. In this case, they are called template parameters. The container adaptors std::stack, std::queue, and std::priority_queue use per default a std::deque to hold their arguments, but you can use a different container.


2 Answers

In C using a zero-sized array as the last member of a struct is actually legal and is commonly used when the struct is going to end up with some sort of dynamically-created inline data that's not known at compile-time. In other words, I might have something like

struct MyData {
    size_t size;
    char data[0];
};

struct MyData *newData(size_t size) {
    struct MyData *myData = (struct MyData *)malloc(sizeof(struct MyData) + size);
    myData->size = size;
    bzero(myData->data, size);
    return myData;
}

and now the myData->data field can be accessed as a pointer to the dynamically-sized data

That said, I don't know how applicable this technique is to C++. But it's probably fine as long as you never subclass your class.

like image 84
Lily Ballard Avatar answered Oct 05 '22 23:10

Lily Ballard


Zero-sized arrays are actually illegal in C++:

[C++11: 8.3.4/1]: [..] If the constant-expression (5.19) is present, it shall be an integral constant expression and its value shall be greater than zero. The constant expression specifies the bound of (number of elements in) the array. If the value of the constant expression is N, the array has N elements numbered 0 to N-1, and the type of the identifier of D is “derived-declarator-type-list array of N T”. [..]

For this reason, your class template cannot be instantiated with arguments 0,0 in GCC 4.1.2 nor in GCC 4.7.2 with reasonable flags:

template< int NINT, int NR0 >
class EncapsulatedObjectBase
{
   public:

  EncapsulatedObjectBase();

  ~EncapsulatedObjectBase();

  double m_real[NR0];
  int m_int[NINT];
};

int main()
{
   EncapsulatedObjectBase<0,0> obj;
}

t.cpp: In instantiation of 'EncapsulatedObjectBase<0, 0>':
t.cpp:17: instantiated from here
Line 10: error: ISO C++ forbids zero-size array
compilation terminated due to -Wfatal-errors.

clang 3.2 says:

source.cpp:10:17: warning: zero size arrays are an extension [-Wzero-length-array]

(Note that, in any case, you won't get any error until you do try to instantiate such a class.)

So, is it a good idea? No, not really. I'd recommend prohibiting instantiation for your class template when either argument is 0. I'd also look at why you want to have zero-length arrays and consider adjusting your design.

like image 43
Lightness Races in Orbit Avatar answered Oct 06 '22 00:10

Lightness Races in Orbit