7
template < unsigned int i >
struct t {
  static const char *s;
};
template < unsigned int i >
const char* t<i>::s = ...;

where ... is "0 1 2 ... i-1", for example "0 1 2 3 4" for i == 5.

Is this possible? (no solutions doing this at run-time, please!)

  • Question asked out of curiosity (doing it with preprocessor macros / constants would be easy, but how about template parameters)?
  • The meaning is: compile-time generated string literal. I see now that const does not force this, but could take any run-time evaluated function for string generation.
Thomas
  • 175
  • 1
  • 7
  • 1
    This is simply not possible. What is it you are trying to achieve by this? – Moo-Juice Nov 04 '10 at 11:26
  • 1
    @Moo: curiosity, no usage yet. If `i` was a preprocessor constant, it would be easy, but with templates I could not think of a way to solve this. – Thomas Nov 04 '10 at 11:35
  • @Reno: Template, because there might be multiple instantiations for various `i`; how could I rewrite this without a struct? I don't think that `template < int i > const char* s = ...;` would compile. – Thomas Nov 04 '10 at 11:37

8 Answers8

6

This is technically possible, it's just very very ugly. Here's a sample that generates a string literal for an unsigned int. It does not (yet) create a string of the form "1 2 3 ... i-1", however I'm sure it is possible if you're willing to spend the effort.

#include <iostream>
#include <string>
#include <limits>

///////////////////////////////////////////////////////////////////////////////
// exponentiation calculations
template <int accum, int base, int exp> struct POWER_CORE : POWER_CORE<accum * base, base, exp - 1>{};

template <int accum, int base>
struct POWER_CORE<accum, base, 0>
{
    enum : int { val = accum };
};

template <int base, int exp> struct POWER : POWER_CORE<1, base, exp>{};

///////////////////////////////////////////////////////////////////////////////
// # of digit calculations
template <int depth, unsigned int i> struct NUM_DIGITS_CORE : NUM_DIGITS_CORE<depth + 1, i / 10>{};

template <int depth>
struct NUM_DIGITS_CORE<depth, 0>
{
    enum : int { val = depth};
};

template <int i> struct NUM_DIGITS : NUM_DIGITS_CORE<0, i>{};

template <>
struct NUM_DIGITS<0>
{
    enum : int { val = 1 };
};

///////////////////////////////////////////////////////////////////////////////
// Convert digit to character (1 -> '1')
template <int i>
struct DIGIT_TO_CHAR
{
    enum : char{ val = i + 48 };
};

///////////////////////////////////////////////////////////////////////////////
// Find the digit at a given offset into a number of the form 0000000017
template <unsigned int i, int place> // place -> [0 .. 10]
struct DIGIT_AT
{
    enum : char{ val = (i / POWER<10, place>::val) % 10 };
};

struct NULL_CHAR
{
    enum : char{ val = '\0' };
};

///////////////////////////////////////////////////////////////////////////////
// Convert the digit at a given offset into a number of the form '0000000017' to a character
template <unsigned int i, int place> // place -> [0 .. 9]
    struct ALT_CHAR : DIGIT_TO_CHAR< DIGIT_AT<i, place>::val >{};

///////////////////////////////////////////////////////////////////////////////
// Convert the digit at a given offset into a number of the form '17' to a character

// Template description, with specialization to generate null characters for out of range offsets
template <unsigned int i, int offset, int numDigits, bool inRange>  
    struct OFFSET_CHAR_CORE_CHECKED{};
template <unsigned int i, int offset, int numDigits>                
    struct OFFSET_CHAR_CORE_CHECKED<i, offset, numDigits, false> : NULL_CHAR{};
template <unsigned int i, int offset, int numDigits>                
    struct OFFSET_CHAR_CORE_CHECKED<i, offset, numDigits, true>  : ALT_CHAR<i, (numDigits - offset) - 1 >{};

// Perform the range check and pass it on
template <unsigned int i, int offset, int numDigits>
    struct OFFSET_CHAR_CORE : OFFSET_CHAR_CORE_CHECKED<i, offset, numDigits, offset < numDigits>{};

// Calc the number of digits and pass it on
template <unsigned int i, int offset>
    struct OFFSET_CHAR : OFFSET_CHAR_CORE<i, offset, NUM_DIGITS<i>::val>{};

///////////////////////////////////////////////////////////////////////////////
// Integer to char* template. Works on unsigned ints.
template <unsigned int i>
struct IntToStr
{
    const static char str[];
};

template <unsigned int i>
const char IntToStr<i>::str[] = 
{
    OFFSET_CHAR<i, 0>::val,
    OFFSET_CHAR<i, 1>::val,
    OFFSET_CHAR<i, 2>::val,
    OFFSET_CHAR<i, 3>::val,
    OFFSET_CHAR<i, 4>::val,
    OFFSET_CHAR<i, 5>::val,
    OFFSET_CHAR<i, 6>::val,
    OFFSET_CHAR<i, 7>::val,
    OFFSET_CHAR<i, 8>::val,
    OFFSET_CHAR<i, 9>::val,
    NULL_CHAR::val
};


///////////////////////////////////////////////////////////////////////////////
// Tests
int _tmain(int argc, _TCHAR* argv[])
{
    std::wcout << IntToStr<17>::str << std::endl;
    std::wcout << IntToStr<173457>::str << std::endl;
    std::wcout << IntToStr< INT_MAX >::str << std::endl;
    std::wcout << IntToStr<0>::str << std::endl;
    std::wcout << IntToStr<1>::str << std::endl;
    std::wcout << IntToStr<-1>::str << std::endl;

    return 0;
}
Jon
  • 3,065
  • 1
  • 19
  • 29
  • The OP's problem calls for an array with size that is at least proportional to the template argument value. I think that's impossible to do in portable standard C++ at the static init phase (although trivial at the dynamic init phase). But I could be wrong... :-) Cheers, – Cheers and hth. - Alf Nov 04 '10 at 20:51
  • @Alf in C++0x it's trivial to do for static init phase to. Too bad we are still in 2010! – Johannes Schaub - litb Nov 04 '10 at 22:26
3

No, but this is possible :

template < unsigned int i >
struct t {
  static std::string s;

  static std::string ConvertIntToString()
  {
    std::stringstream ss;
    ss << i;
    return ss.str();
  }
};

template< unsigned int i >
std::string t< i >::s = t<i>::ConvertIntToStr();

btw why are you using c strings? C++ has std::string class which is superior.

EDIT

I guess you could use template specialization :

template < unsigned int i >
struct t;

template <>
struct t<0>
{
  static const char * const s;
};
const char* const t<0>::s = "abc";

template <>
struct t<1>
{
  static const char * const s;
};
const char* const t<1>::s = "123";
BЈовић
  • 62,405
  • 41
  • 173
  • 273
  • A solution at run-time is of course easy to find, but that was not my question; the point was the `const`. – Thomas Nov 04 '10 at 11:39
  • And of course, I could initialize a `std::string` with the string constant, but first it has to be generated somehow! – Thomas Nov 04 '10 at 11:40
  • Template specialization would not provide a general solution unless it is recursive (but then, I don't see a way to do the recursion, yet) – Thomas Nov 04 '10 at 12:00
  • 1
    If you initialize `std::string` with a string constant, you use all the benefits of compile time statics. The most important benefit of tha latter is that there is no static initialization fiasco for them. – Johannes Schaub - litb Nov 04 '10 at 20:03
  • @Johannes Schaub - litb I am avoiding class static members for that reason, and usually if I need such thing, I create a static function returning a reference, like this struct A{ static int& a(){ static int v=0; return v;} }; – BЈовић Nov 04 '10 at 20:28
1

Impossible.

Because the expansion of template is done at compile time when the compiler can only deal with constant value it knows. Any operation involving memory allocation(e.g. initializing a string) is not possible at this time but only at runtime.

t.g.
  • 1,719
  • 2
  • 14
  • 25
  • But the compiler knows the constant template value, and no (dynamic) memory allocation is necessary. – Thomas Nov 04 '10 at 11:40
  • but it does not know the length char *s, which must be *generated* in memory. that's why string is not allowed as template parameter. – t.g. Nov 04 '10 at 11:47
1

The code you're presenting, ...

template < unsigned int i >
struct t {
  static const char *s;
};
static const char* t::s = ...;

... is invalid. t::s must have external linkage. Also, the definition needs to be templated.

Fixing the direct problems with the code, like ...

template < unsigned int i >
struct T
{
  static const char * const s;
};

template< unsigned i >
const char* const T<i>::s = ...;

... then initializing the T<i>::s with any desired string is trivial.

So, modulo the errors in your code, the answer is "yes, it's not only possible, it's trivial".

But why do you want this Rube Goldberg scheme to accomplish a trivial thing?

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • You're right, the code should only demonstrate the idea, but it was invalid (with c++0x I could directly have written `static const char *s = ...;` inside the struct). I'll correct the code above. Still, I don't see how to initialize the string (and answer my question)! – Thomas Nov 04 '10 at 11:56
  • @Thomas: After the `=` you can write any function call. That answers your original question. The additional question you've later posed in comments, how to do that at compile time, is a bit more tricky (if it is at all possible), but then your motivation for doing that is based on an invalid assumption about `const` requiring compile time evaluation. So, it helps to focus on the real problem to be solved (which you haven't even mentioned), not the vaguely and incorrectly perceived problems of vaguely perceived attempted solution. Cheers & hth., – Cheers and hth. - Alf Nov 04 '10 at 12:00
  • @Alf: where do you expect the function to allocate the result string? In heap or in stack? – Vlad Nov 04 '10 at 12:05
  • @Alf: My thought behind the question was to generate the string at compile time (that's why I called it "literal") - my error was to assume that `const` could force this. – Thomas Nov 04 '10 at 12:10
  • @Vlad: really, that's a silly question. First, the result string can not be stored "in the stack", except if you want Undefined Behavior. But apart from that it can be stored statically or it can be dynamically allocated. E.g. if the string is stored as a static `std::string` then it depends on the `std::string` implementation (which might do small buffer optimization) and the size, and perhaps the phase of the moon. But, you as the programmer can take charge of this, so it's up to you (except don't choose UB). Cheers & hth., – Cheers and hth. - Alf Nov 04 '10 at 12:13
  • @Alf: you cannot do static storage, as there is potentially unbounded number of possible template instantiations. Therefore the only alternative is dynamic allocation, which will be in the best case just reported as a memory leak. Moreover, such function cannot be optimized out by the compiler to avoid the need to initialize (with the possibility to fail) at runtime. – Vlad Nov 04 '10 at 12:40
  • 2
    @Vlad: sorry, none of what you write is meaningful. It's rubbish. – Cheers and hth. - Alf Nov 04 '10 at 12:42
  • @Alf: just try to make an example of the "any function call" after `=`, you'll see the problem yourself. :-P For example, you can try to implement what the OP actually asked for. – Vlad Nov 04 '10 at 12:51
  • 1
    @Vlad: please stop making nonsense comments. None of what you written so far makes any sense whatsoever. The things you say are impossible are trivial; the reasoning you present is invalid; so on, it's rubbish. – Cheers and hth. - Alf Nov 04 '10 at 12:54
  • @Alf: Had it been so trivial, you could present an example. BTW, [this link](http://en.wikipedia.org/wiki/Politeness) might be interesting for you. Being sure that you are not familiar with the concept under the link, I treat our forthcoming communication undesirable. – Vlad Nov 04 '10 at 14:34
  • 1
    @Vlad: please stop posting nonsense comments, nonsense claims, and nonsense challenges. I don't know what the f*** you're after, whether upmanship or dominance game or having others do your homework for you, but I won't. I'm just asking you, politely enough, to stop spewing nonsense and disinformation, please. TIA., – Cheers and hth. - Alf Nov 04 '10 at 14:59
  • @Alf it may be trivial to initialize them during the dynamic initialization phase. But doing this so they are statically initialized is hardly possible, I think. If it is, then you are free of static initialization order fiasco, which would be a good goal. – Johannes Schaub - litb Nov 04 '10 at 20:06
  • @Johannes: right, static initialization phase is a totally different ballgame. The fundamental problem with initialization at the static initialization phase is to produce an initialized array (of whatever type) with content as a function of template parameters. Trivial for the dynamic initialization phase, but I think impossible in portable standard C++ for the static init phase. OTOH., I don't think a portable solution to that would help with the static initialization order fiasco. That problem is tied to the dynamic initialization phase. Cheers, – Cheers and hth. - Alf Nov 04 '10 at 20:25
1

I'd think it might be doable with variadic templates. I don't have a compiler to test with, but I'd imagine something along the lines of this might work.

template < char ... RHS,  unsigned int i> 
struct t { 
    static const char s[] = t<' ', char(i+'0'), RHS, i-1>::s;
}; 

template <char ... RHS > 
struct t<RHS, 0> { 
    static const char s[] = {'0', RHS, '\0'};
}; 

void main() {
    std::cout << t<5>::s; // {'0',' ','1',' ','2',' ','3',' ','4',' ','5','\0'}
}
0

This is not possible using template. But using stringstream, creating such string is trivial. Here is the pseudo code:

string makeit(int i)
{
    stringstream sstr;

    for (int x = 0; x < i-1; x++)
        put x and ' ' in sstr;
    put i in sstr;
    return sstr contents converted to string
}

More information about stringstream can be found here.

Donotalo
  • 12,748
  • 25
  • 83
  • 121
0
//using lambda
#include <sstream>
template<size_t i372> struct T369 {
    static std::string s;
};
template<size_t i372> std::string T369<i372>::s = [](){std::stringstream ss; 
for (int j = 0; j < i372; j++) { ss << "," << j; }; return ss.str(); }();
edison
  • 56
  • 1
  • 3
0

With modern C++ this is now possible.

I believe it can be done with C++17, but this solution uses some C++20 features:

#include <iostream>
#include <concepts>

template <char... Cs>
struct char_pack {
    using self = char_pack<Cs...>;

    static constexpr size_t size = sizeof...(Cs);

   private:
    // This allows us to use ::concat on types that inherit from char_pack<...>,
    // such as int_to_char_pack.
    // We need this because Cs (below) can't be deduced from a derived type.
    //
    // Ex:
    // char_pack<'a', 'b', 'c'>::concat<int_to_char_pack<123>>
    template <typename Other>
    struct concat_impl : concat_impl<typename Other::self> {};

    template <char... OtherCs>
    struct concat_impl<char_pack<OtherCs...>> : char_pack<Cs..., OtherCs...> {};

   public:
    // Using a type alias means we don't have to write ::self in
    // certain places that we otherwise would have needed to due
    // to some quirks in the template evaluation system.
    template <typename Other>
    using concat = concat_impl<Other>;

    template <char... OtherCs>
    using append = char_pack<Cs..., OtherCs...>;

    static constexpr const char to_string[size + 1] = {Cs..., '\0'};
};

template <auto I>
struct int_to_char_pack : char_pack<> {};

template <std::integral IT, IT I>
requires(I >= 10)
struct int_to_char_pack<I> : int_to_char_pack<I / 10>::append<'0' + (I % 10)> {};

template <std::integral IT, IT I>
requires(I < 10 && I >= 0)
struct int_to_char_pack<I> : char_pack<'0' + (I % 10)> {};

template <std::integral IT, IT I>
requires(I < 0)
struct int_to_char_pack<I> : char_pack<'-'>::concat<int_to_char_pack<-I>> {};

template <int I>
struct num_list : num_list<I - 1>::append<' '>::concat<int_to_char_pack<I>> {};

template <>
struct num_list<0> : char_pack<'0'> {};

int main() {
    std::cout << num_list<10>::to_string;
}
Benjy Wiener
  • 1,085
  • 2
  • 9
  • 27