typedef checking by compiler


I just spent a long time isolating a hard to detect bug in my code and am now attempting to prevent this from happening again in the future.

Somewhere in a header I have:

typedef unsigned int parameter;
typedef unsigned int variable;

Later on I have class members that look like:

variable x , y , z; // These lines are usually quite long
parameter a , b , c;

followed by ( in a constructor ):

add_parameter( "a" , &a );
add_variable ( "x" , &x );
add_variable ( "b" , &b ); // BUG

I have a lot of code that looks like this so I generally produce it with lots of cutting and pasting. The problem is that occasionally I accidentally mix up the parameter / variable functions (which is definitely an error) but the compiler is not able to spot the "type" error because the two types are typedef'd to the same underlying type. What I would really like is e.g. for the program below to fail to compile and the compiler to point me to the marked lines.

#include <iostream>

typedef unsigned int parameter;
typedef unsigned int variable;

void print_parameter( parameter p ) {
std::cout << "Parameter has value: " << p << std::endl;
}

void print_variable( variable v ) {
std::cout << "Variable has value: " << v << std::endl;
}

int main( void ) {

parameter p1;
variable v1;

print_parameter( p1 );
print_variable( v1 );

print_parameter( v1 ); // I want compiler errors here
print_variable( p1 ); // and here.

return 0;
}

Is there a simple way to achieve this? I considered wrapping unsigned int in a class definition. However, I wasn't sure how to do this without some performance impact. The code is for solving large systems of ODEs so performance is a big factor and these "parameter"s and "variable"s are used heavily inside the inner-most loops.

Thanks.



Is there a simple way to achieve this? I considered wrapping unsigned int in a class definition. However, I wasn't sure how to do this without some performance impact.


This is the only way I can think of to do it. However, since this is a compile time check... it can be a compile-time option, which would make performance a nonissue. Example:

1
2
3
4
5
6
7
#ifdef CHECK_VARPARAM_DETAILS
  typedef some_variable_class   variable;
  typedef some_parameter_class  parameter;
#else
  typedef unsigned int          variable;
  typedef unsigned int          parameter;
#endif 


'some_xxx_class' would probably need to be pretty convoluted in order to compile without errors. That is, you'd probably need to overload all sorts of mathematical and casting operators. If you run the program with the classes like this, you will get a performance hit -- but if the goal is to just do compile checks, you'll never have to run the program. You just check to make sure it compiles okay -- and if it does, undefine the CHECK_VARPARAM_DETAILS option. And if it doesn't -- then your bugs will be exposed.

To avoid writing two versions of the same class you can derive both classes from a common class that has the operators overloaded:

1
2
3
4
5
6
7
8
9
10
11
12
13
class some_base_class
{
public:
  // overload operators and junk here

protected:
  unsigned int v;
};

class some_variable_class : public some_base_class
{
  // only need to make copy ctor
};
Last edited on
This solution works perfectly.

Actually these objects only ever need to be used to access a custom array class. So the class definition is pretty much empty and I only need four overloads in the array class (number is unsigned int):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

// safe wrappers around unsigned int
#ifdef _DEBUG
struct parameter
{
public:
        number n;
        parameter( const number& _n ): n(_n) {};
};
struct variable
{
public:
        number int n;
        variable( const number& _n ): n(_n) {};
};
#else
typedef number parameter;
typedef number variable;
#endif /* _DEBUG */

class Array<T>
{
           // otherstuff
#ifdef _DEBUG
        // typesafe overloads in the Array<T> class header
        const T& operator[] (parameter p) const { return (*this)[p.n]; };
        const T& operator[] (variable  v) const { return (*this)[v.n]; };
              T& operator[] (parameter p)       { return (*this)[p.n]; };
              T& operator[] (variable  v)       { return (*this)[v.n]; };

#endif /*_DEBUG */
       // other stuff
};


I regularly compile in debug mode so it will never be long before I pick up one of these errors.
Interestingly, a quick test seems to suggest that g++ with the -O2 option can optimise the array accesses to the same speed as simply using an unsigned int anyway. I would prefer not to rely on this, however, so I will use the solution proposed.

Thanks for the quick response.

p.s.
In the interest of completion the solution for the program I originally posted looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <iostream>

// Wrapper around unsigned int
#ifdef CHECK_PARVAR
class parameter {
public:
        unsigned int n;
};
class variable {
public:
        unsigned int n;
};

// Overload the functionality we want from parameter and variable
std::ostream& operator<<(std::ostream& os, const parameter& p)
{
  os << p.n;
  return os;
}
std::ostream& operator<<(std::ostream& os, const variable& p)
{
  os << p.n;
  return os;
}
#else
typedef unsigned int parameter;
typedef unsigned int variable;
#endif // CHECK_PARVAR

// Define functions that fail with the wrong arg type
// when CHECK_PARVAR is defined.
void print_parameter( parameter p ) {
        std::cout << "Parameter has value: " << p << std::endl;
}

void print_variable( variable v ) {
        std::cout << "Variable has value: " << v << std::endl;
}

int main( void ) {
        
        parameter p1;
        variable v1;

        print_parameter( p1 ); // OK
        print_variable( v1 );  // OK
        
        print_parameter( v1 ); // Now I get errors here
        print_variable( p1 );  // and here.

        return 0;
}




Topic archived. No new replies allowed.