Thomas
Thomas

Reputation: 185

c++: generating string literals from template parameters

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!)

Upvotes: 8

Views: 4076

Answers (8)

Benjy Wiener
Benjy Wiener

Reputation: 1255

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;
}

Upvotes: 0

edison
edison

Reputation: 56

//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(); }();

Upvotes: 0

mooing_duck
mooing_duck

Reputation: 11

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'}
}

Upvotes: 1

Jon
Jon

Reputation: 3065

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;
}

Upvotes: 6

Cheers and hth. - Alf
Cheers and hth. - Alf

Reputation: 145249

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?

Upvotes: 1

BЈовић
BЈовић

Reputation: 64223

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";

Upvotes: 3

Donotalo
Donotalo

Reputation: 13025

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.

Upvotes: 0

t.g.
t.g.

Reputation: 1749

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.

Upvotes: 1

Related Questions