views:

469

answers:

3

I am trying to play fancy games which have the C++ compiler synthesize hash values of constant strings at compiletime. This would let me replace the string with a single identifier, with a massive savings in code size and complexity.

For programming clarity and ease, it'd be awesome if I could examine and compute at compiletime with simple inline character strings like "Hello" which are compiletime constant pointers to compiletime constant chars.

If I can index into these at compiletime, I can make a template metaprogram to do what I want. But it is unclear if the C++ standard treats a ct-constant index of a ct-constant array as ct-constant by itself.

Asked another way,

 const char v="Hello"[0];

is quite valid C++ (and C). But is the value v a compile time constant?

I already believe the answer is no, but in practice some compilers accept it without even any warning, much less error. For example, the following compiles and runs without even a single warning from Intel's C++ compiler:

#include <iostream>
const char value="Hello"[0];
template<char c>  void printMe()
{
  std::cout << "Template Val=" << c << std::endl;
}

int main()
{
  const char v='H';
  printMe<'H'>();
  printMe<v>();
  printMe<value>(); // The tricky and interesting case!
}

However, Microsoft's compiler will not compile at all, giving a reasonably coherent error message about using a template with an object with internal linkage.

I suspect the answer to my question is "No, you can't assume any array reference even to a constant array with a constant index is constant at compiletime". Does this mean the Intel compiler's successful execution is a bug in the Intel compiler?

+2  A: 

It doesn't work on GCC either.

However, outside of a language-compliance viewpoint, it's nice that the compiler optimiser does treat it as a character constant, pretty much. I exploited that fact to allow preprocessor-generated character constants (by using *#foo). See http://cvs.openbsd.org/cgi-bin/query-pr-wrapper?full=yes&amp;numbers=1652, in file hdr.h. With that macro, you could write

DECR(h, e, l, l, o)

rather than

DECR('h', 'e', 'l', 'l', 'o')

Much more readable, in my view. :-)

Chris Jester-Young
Clever macro hack to use the stringize macro operator to charize raw chars! Of course this doesn't help much when trying to use a template argument. Unless maybe a varargs style macro (C99??) could strip off one char, build a new typedef and include the remaining var args as a recursive type a-la Loki?
SPWorley
+1  A: 

Good question, yes this can be done, and its fine with the standards, and it'll work on Microsoft, GCC, and Intel, problem is you have the syntax wrong :)

One second I'll cook up a sample... Ok done, here it is. This sample is valid C++, and I've used it quite often, but indeed most programmers don't know how to get it right.

template<char* MSG>
class PrintMe
{
public:
    void operator()()
    {
     printf(MSG);
    }
};

char    MyMessage[6] = "Hello"; //you can't use a char*, but a char array is statically safe and determined at compiletime

int main(int argc, char* argv[])
{
    PrintMe<MyMessage> printer;
    printer();
    return 0;
}
Robert Gould
This isn't accessing the DATA at compiletime, just the char pointer. So for example, after the printer(); call you can add MyMessage[0]++; printer(); and you'll see the data updated. This means the data is being accessed at runtime, not compiletime. But your example is still interesting in that a non-constant pointer can be passed as a template argument.. rather surprising by itself!
SPWorley
That mean's I'm not sure what you wanted to do, but if I can do the above, you might be able to do what you want, can you give a better explanation of what your last template's expected behavior is?
Robert Gould
Ok, now I think I get it, but no you can't do it. Since it actually depends on the endianness of the hardware among other things
Robert Gould
Hmm, I keep thinking about your example code. It's VERY surprising to me you can pass a pointer to a char array as a compiletime template argument! And even more surprising that you CAN'T pass a const pointer to a statically defined inline string constant! I also notice you have to define the character array on a global scope, if it's local to the function it won't work! This is all kind of mysterious, can any global-scope object pointer be passed as a template arg? Very interesting!
SPWorley
The code above was the result of lots of investigation, and as you mention it has to be global (but I normally place the arrays within namespaces, to keep things clean)I use this technique to store metadata in templates a lot. However it'll only work with global scope POD pointers as far as I know. However it might just work on any object on some compilers
Robert Gould
A: 

The relevant difference here is the difference between a "Integral Constant Expression" and a mere compile-time constant. "3.0" is a compile-time constant. "int(3.0)" is a compile-time constant, too. But only "3" is an ICE. [See 5.19]

More details at boost.org

MSalters
Thanks for the reference but it doesn't seem to apply. In the example code, const char value="Hello"[0]; defines value to be an integral constant expression (as the Boost page shows as example 3). Yet the value is rejected as a template argument, so Integral Constant Expressions aren't compiletime constants usable in templates.
SPWorley
Good point, but not relevant. A global constant of integer type is an ICE if and only iff it's initialized with an ICE itself. For instance, const char value = rand(); is not an ICE either, but it also looks like example 3 from Boost. Your problem is the same, "Hello"[0] isn't an ICE to start with, therefore you can't initialize other ICE's with it.
MSalters