function ndindex( x ) {
	var opts;
	var err;
	var arr;
	var dt;
	var t;
	var v;
	if ( !(this instanceof ndindex) ) {
		if ( arguments.length > 1 ) {
			return new ndindex( x, arguments[ 1 ] );
		}
		return new ndindex( x );
	}
	if ( !isndarrayLike( x ) ) {
		throw new TypeError( format( 'invalid argument. First argument must be an ndarray-like object. Value: `%s`.', x ) );
	}
	// Resolve index options:
	opts = defaults();
	if ( arguments.length > 1 ) {
		err = validate( opts, arguments[ 1 ] );
		if ( err ) {
			throw err;
		}
	}
	// Normalize the input ndarray-like object by converting to a "base" ndarray:
	arr = ndarraylike2ndarray( x );

	// Resolve the input ndarray data type:
	dt = dtype( arr ); // note: as we've normalized to a "base" ndarray, there should always be a resolved data type

	// When provided a "generic" ndarray, attempt to infer the type of index ndarray...
	if ( dt === 'generic' ) {
		if ( arr.length > 0 ) {
			v = arr.iget( 0 ); // note: we assume that, even for zero-dimensional ndarrays, we can pass `0` as an argument to `#.iget`

			// Infer the "type" of index array from the first element...
			if ( isBoolean( v ) ) {
				t = 'bool';
			} else if ( isInteger( v ) ) {
				t = 'int';
			} else {
				throw new TypeError( 'invalid argument. First argument must be a valid index ndarray.' );
			}
		} else {
			// If we've been provided an empty ndarray, fallback to a default index type:
			t = 'int';
		}
	} else if ( dt === 'int64' || dt === 'int32' ) {
		t = 'int';
	} else if ( dt === 'uint8' ) {
		t = 'mask';
	} else if ( dt === 'bool' ) {
		t = 'bool';
	} else {
		throw new TypeError( 'invalid argument. First argument must be a valid index ndarray.' );
	}
	if ( t !== 'int' && opts.kind !== '' ) { // note: here we assume that only integer ndarrays can have specialized "kinds"
		throw new TypeError( format( 'invalid argument. First argument is not compatible with the specified index "kind". Type: %s. Kind: %s.', t, opts.kind ) );
	}
	// Add the ndarray index to the index cache:
	cache.push({
		'id': generateId(),
		'ref': this,
		'data': arr, // note: while we cache a reference to the normalized ndarray object, and not the original input ndarray-like object, we still hold onto memory, as the normalized object references the same underlying data buffer
		'type': t,
		'kind': opts.kind,
		'dtype': dt,
		'persist': opts.persist
	});

	// Store a reference to the cache node:
	setReadOnly( this, '_node', cache.last() );

	// Initialize a boolean flag indicating whether an index object has been invalidated (i.e., freed):
	setNonEnumerable( this, '_invalidated', false );

	return this;
}
ndarray index constructor.

@constructor
@param {ndarray} x - input ndarray
@param {Options} [options] - function options
@param {boolean} [options.persist=false] - boolean indicating whether to continue persisting an index object after first usage
@param {string} [options.kind=''] - specifies whether a provided ndarray is a specialized kind of integer input ndarray
@throws {TypeError} first argument must be an ndarray-like object
@throws {TypeError} first argument must be a valid index ndarray
@throws {TypeError} first argument must be compatible with a specified index "kind"
@throws {TypeError} options argument must be an object
@throws {TypeError} must provide valid options
@returns {ndindex} ndindex instance

@example
var empty = require( '@stdlib/ndarray/empty' );

var x = empty( [ 10 ], {
    'dtype': 'uint8'
});

var idx = new ndindex( x );
// returns <ndindex>