Advanced Declarations

Just like C/C++ (and even more so) Jancy allows declaring quite sophisticated entities in one go; therefore, declarations in Jancy can be rather complex, and both specifier and declarator can be composite.

Specifiers can contain:

  • Access specifiers (public or protected)

  • Storage specifiers (e.g. static, typedef, virtual etc)

  • Type specifiers (e.g. char, int, double etc)

  • Type modifiers (e.g. const, volatile, unsigned etc)

Declarators can contain:

  • Type modifiers (e.g. const, volatile, unsigned etc)

  • Pointer prefixes (*)

  • Declarator identifiers (e.g. myVariable)

  • Declarator special function kind (e.g. construct, get, set etc)

  • Array suffixes (e.g. [10])

  • Function suffixes (e.g. (int, double))

  • Bitfied suffixes (e.g. : 10)

  • Initializers (e.g. = 0)

  • Function bodies (e.g. { return 0; })

A realistic example of a complex declaration could look like:

static int const* function* a[2]() = { foo, bar };

Here static is a storage specifier, int is a type specifier, const and function are type modifiers, two * (asterisks) are pointer prefixes, a is a declarator name, [2] is a array suffix, () is a function suffix and = { foo, bar } is an initializer. This line declares a static variable a as an array of two elements of type “pointer to a function which takes no arguments and returns a const-pointer to int and initializes it with pointers to functions foo and bar. Wheh! Seriously, it’s much easier to read the code itself than its explanation.

C/C++ equivalent of the above example would look like:

static int const* (*a[2])() = { foo, bar };

Now, if we add one extra layer of function pointers, C/C++ falls short of declaring it in one go (you will receive function returns function error); Jancy syntax still allows to do so (not like that could be crucial in any realistic scenario; just a small demonstration of flexibility):

static int const* function* function* a[2]()(int);

There are no nested declarators in Jancy. Nested declarators in C/C++ emerged as a solution (and in my personal opinion, not an elegant one) to the problem of resolving ambiguities in complex pointer-to-function declarations. Like you just saw, Jancy uses a different approach with type modifiers function, property, array:

int property* function* array* a[2][3]();

Here a is an array of three elements of type pointer to array of two elements of type pointer to a function taking no arguments and returning a pointer to int property. Speaking formally, the rules for reading Jancy declaration are as follows. First, you start unrolling declarator’s pointer prefixes right-to-left. If type modifiers of a pointer prefix requires a suffix, you unroll the first suffix. After all pointer prefixes are unrolled, you unroll the remaining suffixes.

In reality, however, you are unlikely going to need mind-boggling declarations like the one above. It’s always possible to split an overcomplicated declaration into two (or more) using good-old typedefs: just like in C/C++, Jancy declarations with typedef storage specifier result in creation of a type alias:

typedef double DoubleBinaryFunc (double, double);
DoubleBinaryFunc* funcPtr; // use new typedef to declare a variable

typedef int IntArray[10][20];
IntArray foo();  // use new typedef as a retval type

There are other important differences with C/C++. In Jancy named type declaration is not a type specifier. The following code, perfectly valid in C/C++ will produce an error in Jancy:

struct Point {
    int m_x;
    int m_y;
} point;

In Jancy you cannot declare a named type and immediatly use it to declare a variable or a field. Therefore, to fix previous example, we need to simply split a single declaration into two:

struct Point {
    int m_x;
    int m_y;
}

Point point;

Note that declaration of a named type does not need to end with a semicolon (needless to say, it will also compile should you add a semicolon).

Jancy does not require declaration-before-usage at global scope. Therefore, there is no need to create so-called forward declarations of functions or types, so the following example will compile in Jancy, but not in C/C++:

void foo() {
    A a;
    B b;
}

struct A {
    B* m_b;
}

struct B {
    A* m_a;
}