May 2008 Archives

2008-05-26

template type arguments

To gain code locality I often use local types. I really like this feature as it allows me to define a type a the location where it is needed.

A frequent use for this would be compare or ordering functors for standard algorithms like sort or standard containers like hash_maps. This look's like this:

void foo(const std::vector<Bar> bars)
{
    struct Sort {
        bool operator()(const Bar& lhs, const Bar& rhs) const 
        { /* ... */ }
    };
    std::sort(bars.begin(), bars.end(), Sort());
}

Other languages have code blocks or other features which allow you to write the sort criteria straight away. Local types are an tolerable alternative to this in my eyes.

Ähm, I mean, they could have been. But the standard prevents this. Section 14.3.1.2 says:

A local type, a type with no linkage, an unnamed type or a type compounded from any of these types shall not be used as a template-argument for a template type-parameter.

Oh my noodles. Why? Does anybody know a good reason for this? I can't imagine any. Furthermore I wonder how the syntax would look like when using an unamed type as a template parameter.

To make the confusion complete the GNU C++ compiler (version 4.2.3) dumps the following error when compiling the above example.

error: no matching function for call to 'sort(__gnu_cxx::__normal_iterator<Bar*, std::vector<Bar, std::allocator<Bar> > >, __gnu_cxx::__normal_iterator<Bar*, std::vector<Bar, std::allocator<Bar> > >, foo(std::vector<Bar, std::allocator<Bar> >&)::Sort&)'
Yeah, right.

Posted by Alexander Bernauer | Permanent Link | Categories: C++

2008-05-26

anonymous classes

Recently I tend to group semantically related members into nested classes or structs. For instance imagine a class with concurrent access:

class Foo {
public:
    int getSomething() {
        Lock l(purpose.mutex);
        return purpose.something;
    }
private:
    struct {
        Mutex mutex;
        int something;
    } purpose;
    
};

To avoid redundancy I don't name such nested structs. Why should I? C++ provides the feature of anonymous classes and structs and this technique is a good example why one would like to have it.

But as often with C++ this language feature collides with others and makes it less usable. In this case you are getting problems when the grouped members need to be initialized. As a constructor must have the name of the class anonymous classes can not have constructors.

Orthogonality must definitly be a design goal for usable general purpose languages. And this property is greatly missing in C++ which is the matter for my blog entries.


Posted by Alexander Bernauer | Permanent Link | Categories: C++

2008-05-26

signed or unsigned

I'm programing in C++ for years now and I still experience frequent surprises. Even on topics where I was dead sure.

Is an int signed or unsigned? The standard clearly says (section 3.9.1.2)

There are four signed integer types: "signed char", "short int", "int", and "long int."
So writing just int means the same as signed int which is as expected.

And here is the surprise: this is not always true. Section 9.6.3 says:

It is implementation-defined whether a plain (neither explicitly signed nor unsigned) char, short, int or long bit-field is signed or unsigned.

Unbelievable... It would have been too easy otherwise, I guess...

Thanks, Roker


Posted by Alexander Bernauer | Permanent Link | Categories: C++

2008-05-26

ain't a typedef

typedefs are a convenient way to give semantics to variables and to stay flexible for future adjustments of the actual used type.

For example if you deal with navigation software you might have some typedefs for Direction and Speed which could be simply ints. And you might have a typedef for Position which is a std::pair<int32_t, int32_t> so that all valid GPS positions of some required resolution can be represented.

Say you want to have some toString or operator<< methods for debuging purposes. So you could have a call trace of your program like this.

std::ostream& operator<<(std::ostream& oss, Direction direction) {
    oss << "Direction=" << int(direction) << " degree";
    return oss;
}

std::ostream& operator<<(std::ostream& oss, Speed speed) {
    oss << "Speed=" << int(speed) << " mps";
    return oss;
}

void someFunction(Speed speed, Direction direction) {
    logger << "someFunction(" << speed << ", " << direction << ")";
    //...
}

and get an output like this
someFunction(Speed=23 meter/second, Direction=42 degree)

Looks good, but doesn't work. If Speed and Direction are typedefed to the same or implicitly castable types the problem manifests:

error: ambiguous overload for 'operator<<'

Unfortunately, altough the keyword is named typedef it is no type definition. The standard says in section 7.1.3:

A typedef-name is [...] a synonym for another type. A typedef-name does not introduce a new type the way a class declaration [...] or enum declaration does.

Prohibition of use of function overloading techniques is just one aspect of the problem. In general you have a type with different semantics but you can not treat it differently. This is a frequent source of annoyance.

PS:
Yes, int32_t is not part of ISO-C++. And this is a mistake...


Posted by Alexander Bernauer | Permanent Link | Categories: C++

2008-05-05

a fellow sufferer

defective C++

Thank you, Andreas


Posted by Alexander Bernauer | Permanent Link | Categories: C++

2008-05-05

tell me why!

Most C++ quirks can be argued by efficency or inheritence reasons. But sometimes I just don't see why there is yet another exception in the rules of the language. Such an example is the initialization of static constant data members:

Section 9.4.4 of the ISO standard says:

If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a constant-initializer which shall be an integral constant expression.

Here is an example of this:

class X {
    static const int i = 23;
};

Now, I wonder why this in-place initialization is restricted to those two types. I would like to be a able to express:

class A;
class X {
public:
    static const A* special_meaning=0;
    void do_something(const A* with=special_meaning);
};

C++ prevents me from writing descriptive code in this case. And for what reason? I can't see any.

By the way, section 9.4.4 adds:

The member shall still be defined in a namespace scope if it is used in the program and the namespace scope definition shall not contain an initializer.
Sure, linker and such... So what is this in-place initialization good for after all?

Thank you, Liesa


Posted by Alexander Bernauer | Permanent Link | Categories: C++

2008-05-05

scope jumble

What's the value of X::i?

int g() { return 23;}
struct X {
    static int g() { return 42; }
    static int i;
};
int X::i = g();

The answer is: 42 - says ISO C++ in section 9.4.2. The initialization of the static data member is implicit in the scope of the struct. Oh, yes! The standard's example is even more fun:

int g();
struct X {
        static int g();
};
struct Y : X {
        static int i;
};
int Y::i = g();         // equivalent to Y::g();

Note that X could be definied in another header file than g and that the initialization normaly is in a cc-file. Find, Rover, find!

Thank you, Roker


Posted by Alexander Bernauer | Permanent Link | Categories: C++