Implicit enum conversions are a stupid PITA

Adam D. Ruppe destructionator at gmail.com
Thu Mar 25 22:47:17 PDT 2010


On Thu, Mar 25, 2010 at 04:52:29PM -0500, Andrei Alexandrescu wrote:
> It does work well, but there's one more issue: the type of octal!x is 
> always ulong. To make the code industrial strength, you should observe 
> the usual D rules:

I think I made it work. Also used template constraints to pretty up the
error messages a little on bad strings. The line number is still that of
the template instead of the instantiation, but maybe it is just because
my dmd is a few months old. I seem to recall seeing this improved in the
compiler itself, so I'm not worrying about it here.

The code is way at the bottom of this message. I'm going to show off a bit
first :)


Error messages:

int w = octal!845;
octal.d(106): Error: static assert  "845 is not a valid octal literal"

int w = octal!"spam";
octal.d(106): Error: static assert  "spam is not a valid octal literal"



The type magic works pretty well too:

int w = octal!"017777777777"; // compiles
int w = octal!"020000000000"; // but this doesn't... (this is int.max+1 btw)
octal.d(147): Error: cannot implicitly convert expression (octal()) of type long to int

int w = octal!"1L";
octal.d(147): Error: cannot implicitly convert expression (octal()) of type long to int

int w = octal!"1uL";
octal.d(147): Error: cannot implicitly convert expression (octal()) of type ulong to int

This also works with integer literals instead of strings:
	auto w = octal!1L;
	pragma(msg, typeof(w)); // prints "long"
	auto w = octal!1;
	pragma(msg, typeof(w)); // prints "int"
	auto w = octal!1u;
	pragma(msg, typeof(w)); // prints "uint"


The only bug I'm seeing is if the integer literal is type long,
but once converted to octal it will fit in an int, the template returns a
long. Passing a string does the right thing and returns int, but the literal
doesn't.

I don't see a way to fix this without breaking the octal!1777[...]7L case.
(To make the suffixes on literals work, I did typeof an alias for the helper
template instead of analyzing the string like I did for the main template.
I couldn't think of any other way to make it work; I even tried parsing
stringof the alias, but alas.


Anyway, aside from that, it works perfectly in my battery of tests. Handles
suffixes, embedded underscores, bad strings, types. Pretty cool.



Here's the code, with some comments added too:

===============================

/*
	Take a look at int.max and int.max+1 in octal and the logic for this function follows directly.
*/
template octalFitsInInt(string octalNum) {
	// note it is important to strip the literal of all non-numbers. kill the suffix and underscores
	// lest they mess up the number of digits here that we depend on.
	static if(strippedOctalLiteral(octalNum).length < 11)
		enum bool octalFitsInInt = true;
	else static if(strippedOctalLiteral(octalNum).length == 11 && strippedOctalLiteral(octalNum)[0] == '1')
		enum bool octalFitsInInt = true;
	else
		enum bool octalFitsInInt = false;
}

string strippedOctalLiteral(string original) {
	string stripped = "";
	foreach(c; original)
		if(c >= '0' && c <= '7')
			stripped ~= c;
	return stripped;
}

template literalIsLong(string num) {
	static if(num.length > 1)
		enum literalIsLong = (num[$-1] == 'L' || num[$-2] == 'L'); // can be xxL or xxLu according to spec
	else
		enum literalIsLong = false;
}

template literalIsUnsigned(string num) {
	static if(num.length > 1)
		enum literalIsUnsigned = (num[$-1] == 'u' || num[$-2] == 'u') // can be xxL or xxLu according to spec
		   || (num[$-1] == 'U' || num[$-2] == 'U'); // both cases are allowed too
	else
		enum literalIsUnsigned = false;
}

/**
	These pass out the correctly typed literal based on the specifiers on the end of the string
	and the size of the literal.

	If it can fit in an int, it is an int. Otherwise, it is a long.

	But, if the user specifically asks for a long with the L suffix, always give the long.

	Give an unsigned iff it is asked for with the U or u suffix.
*/

int octal(string num)() if((octalFitsInInt!(num) && !literalIsLong!(num)) && !literalIsUnsigned!(num)) {
	return octal!(int, num);
}

long octal(string num)() if((!octalFitsInInt!(num) || literalIsLong!(num)) && !literalIsUnsigned!(num)) {
	return octal!(long, num);
}

uint octal(string num)() if((octalFitsInInt!(num) && !literalIsLong!(num)) && literalIsUnsigned!(num)) {
	return octal!(int, num);
}

ulong octal(string num)() if((!octalFitsInInt!(num) || literalIsLong!(num)) && literalIsUnsigned!(num)) {
	return octal!(long, num);
}

/*
	Returns if the given string is a correctly formatted octal literal.

	The format is specified in lex.html. The leading zero is allowed, but not required.
*/
bool isOctalLiteralString(string num) {
	if(num.length == 0)
		return false;

	// must start with a number
	if(num[0] < '0' || num[0] > '7')
		return false;
	
	foreach(i, c; num) {
		if((c < '0' || c > '7') && c != '_') // not a legal character
			if(i < num.length - 2)
				return false;
			else { // gotta check for those suffixes
				if(c != 'U' && c != 'u' && c != 'L')
					return false;
				if(i != num.length - 1) { // if we're not the last one, the next one must also be a suffix to be valid
					char c2 = num[$-1];
					if(c2 != 'U' && c2 != 'u' && c2 != 'L')
						return false; // spam at the end of the string
					if(c2 == c)
						return false; // repeats are disallowed
				}
			}
	}

	return true;
}

/*
	Returns true if the given compile time string is an octal literal.
*/
template isOctalLiteral(string num) {
	enum bool isOctalLiteral = isOctalLiteralString(num);
}

/*
	Takes a string, num, which is an octal literal, and returns its value, in the
	type T specified.

	So:

	int a = octal!(int, "10");

	assert(a == 8);
*/
T octal(T, string num)() {
    static assert(isOctalLiteral!num, num ~ " is not a valid octal literal");

    ulong pow = 1;
    T value = 0;

    for(int pos = num.length - 1; pos >= 0; pos--) {
        char s = num[pos];
	if(s < '0' || s > '7') // we only care about digits; skip the rest
		continue; // safe to skip - this is checked out in the assert so these are just suffixes
	
	value += pow * (s - '0');
	pow *= 8;
  }

  return value;
}

import std.metastrings: toStringNow;
import std.traits: isIntegral;

/**
	Constructs an octal literal from a number, maintaining the type of the original number.
	For example:

	octal!10
	
	is exactly the same as integer literal 8.

	octal!10U

	is identical to the literal 8U.
*/
template octal(alias s) if(isIntegral!(typeof(s))) {
	enum auto octal = octal!(typeof(s), toStringNow!(s));
}

unittest
{

	// ensure that you get the right types, even with embedded underscores
	auto w = octal!"100_000_000_000";
	static assert(!is(typeof(w) == int));
	auto w2 = octal!"1_000_000_000";
	static assert(is(typeof(w2) == int));

    static assert(octal!"45" == 37);
    static assert(octal!"0" == 0);
    static assert(octal!"7" == 7);
    static assert(octal!"10" == 8);
    static assert(octal!"666" == 438);

    static assert(octal!45 == 37);
    static assert(octal!0 == 0);
    static assert(octal!7 == 7);
    static assert(octal!10 == 8);
    static assert(octal!666 == 438);

    static assert(octal!"66_6" == 438);

    static assert(octal!2520046213 == 356535435);
    static assert(octal!"2520046213" == 356535435);

    static assert(octal!17777777777 == int.max);

    static assert(!__traits(compiles, octal!823));
//    static assert(!__traits(compiles, octal!"823")); // for some reason, this line fails, though if you try it in code, it indeed doesn't compile... weird.
    static assert(!__traits(compiles, octal!"_823"));
    static assert(!__traits(compiles, octal!"spam"));
    static assert(!__traits(compiles, octal!"77%"));

    int a;
    long b;

    static assert(__traits(compiles,  a = octal!"17777777777")); // biggest value that should fit in an it
    static assert(!__traits(compiles, a = octal!"20000000000")); // should not fit in the int
    static assert(__traits(compiles, b = octal!"20000000000")); // ... but should fit in a long

   static assert(!__traits(compiles, a = octal!"1L"));
   //static assert(!__traits(compiles, a = octal!1L)); // this should pass, but it doesn't, since the int converter doesn't pass along its suffix to helper templates

   static assert(__traits(compiles, b = octal!"1L"));
   static assert(__traits(compiles, b = octal!1L));
}

===============================

-- 
Adam D. Ruppe
http://arsdnet.net



More information about the Digitalmars-d mailing list