DRAFT Improved C and C++ Interaction

Lawrence Crowl, 13 July 2007

One of the strengths of C++ has been its ability to directly access the operating system via its compatibility with C. Furthermore, as other programming languages have provided features to interact with C, C++ has been able to interact with those languages through those same features. Unfortunately, that interaction comes with a high price in expressiveness within C++ code.

Recent developments in the C++ standard enable higher expressiveness while maintaining good interaction. This paper provides an overview of those developments, and presents an example based on the proposed C++ atomic types.

Past Compatibility

In C++98, the interaction with C has been entirely at the C level, through POD (Plain Old Data) types, particularly POD structs. These POD types provide several features and benefits:

byte copyable
use memcpy, buffer handling
C layout compatible
language interoperability, binary I/O
statically initializable
avoid order-of-initialization issues, avoid data races during multi-thread initialization
aggregate initialization
brace initialization syntax

Unfortunately, any of several different language features can make a type non-POD, which then removes these features.

Standard-Layout and Trivial Types

Paper N2294 POD's Revisited; Resolving Core Issue 568 (Revision 4) decomposes the notion of POD types into two notions, standard-layout types and trivial types. Standard-layout types have limititations on data members, that enables them to be layout-compatible with pure C structs. Trivial types permit binary definition and copying. With this decomposition, definition of a POD changes to a type that is both trivial and standard-layout.

Both type notions allow use of features that do not bear directly upon those definitions.

Defaulted and Deleted Functions

While N2294 permits trivial types to have user-defined constructors, the declaration of one inhibits the implicit declaration of the default constructor. If the user needs the default constructor, and defines it, the class is no longer trivial.

The solution is presented in N2326 Defaulted and Deleted Functions in the form of syntax for explicit default definitions.


struct type {
    int f;
    type( int p ) f(p) { }
    type() = default;
};

Such explicitly defaulted definitions remain trivial.

Sometimes the code generated by an implicit copy constructor or an implicit copy assignment operator are semantically wrong. Common practice is to then declare these methods as private and not implement them. However, such methods are non-trivial, and hence the entire type becomes non-trivial.

The solution also appears in N2326 in the form of syntax for explicitly deleting a function definition, making any calls to the function ill-formed.


struct type {
    type( const type & ) = delete;
    type operator =( const type & ) = delete;
};

Deleted functions are trivial, meeting the requirement of trivial types.

Construction as a Constant Expression

Sometimes one would like to statically initialize an object for which an appropriate constructor has been defined.


struct type {
    int f;
    type( int p ) f(p) { }
};
type v( 3 );

In C++98, a user-defined constructor implies executing code, and so such a type would not be statically initializable.

The solution is presented in N2235 Generalized Constant Expressions—Revision 5 in the form of syntax for enabling function calls within constant expressions.


struct type {
    int f;
    constexpr type( int p ) f(p) { }
};

The constexpr on a function definition indicates that the function may be called from within constant expressions, and requires the compiler to evaluate the expression. With a constant expression, the compiler can statically initialized the variable.

Further, the programmer can get a diagnostic when the compiler cannot statically initialize a variable by declaring the variable with constexpr.


constexpr type v( 3 );

Generalized Initialization Syntax

Unfortunately, the above initialization syntax is not C compatible, and could not be used in common headers. The corresponding C initialization would use aggregate initialization.

The solution is presented in N2215 Initializer lists (Rev. 3) which generalizes initialization syntax so that the various syntactic forms of initialization in C++ can be used somewhat interchangably.


type v = { 3 };

Here, the syntax for aggregate initialization matches the parameters for the constuctor, and hence the constructor is invoked, and being constexpr, is evaluated statically.

Example — Atomic Int

The following example is a stripped down version of the type proposed in N2324 C++ Atomic Types and Operations.


typedef struct atomic_int {
#ifdef __cplusplus

    /* The destructor is implicitly declared and defined, and
    therefore is trivial. */

    constexpr atomic_int( int v ) : f( v ) { }
    /* A constructor that accepts the base value.  It is constexpr,
    and therefore may be used in static initialization*/

    atomic_int() = default;
    /* The previous constructor would otherwise suppressed the
    default constructor, so it must be explicitly defaulted, which
    is trivial. */

    atomic_int( const atomic_int & ) = delete;
    atomic_int & operator =( const atomic_int & ) = delete;
    /* Copying atomic variables requires an atomic action over
    two locations, which is not supported in hardware, and should
    therefore be supressed.  The deleted functions are trivial. */

    int operator =( int v ) volatile;
    /* Assigning from (storing) a value is safe and meaningful. */

    operator int() volatile;
    /* The conversion operator eases loading the value from an
    atomic variable. */

    int operator +=( int ) volatile
    /* Lets add a member operator just for fun. */

private:
    /* All of the data members have the same access, and are
    therefore standard layout. */
#endif
    int f;
} atomic_int;

Template Names

One of the main strengths of C++ is generic programming, which means generic names. Unfortunatly, the names acceptable to C are not generic. So, we need to generate a generic name from the C-level name. This name is produced by specializing a generic atomic template on int and then inheriting the C-level atomic type. In the following example, some methods must redefined, but in practice most methods will be inhierited from the base.


template< typename T >
struct atomic< int > : atomic_int {
    constexpr atomic( int v ) : atomic_int( v ) { }
    atomic() = default;
    atomic( const atomic & ) = delete;
    atomic & operator =( const atomic_int & ) = delete;
    int operator =( int v ) volatile;
}

Because this derived class introduces no new non-static data members, and because the special member functions all have trivial definitions, the derived class remains both standard-layout and trivial.