C++:从模板参数生成字符串字面量

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

其中...表示"0 1 2 ... i-1",例如当i == 5时,它表示"0 1 2 3 4"。

这种方法可行吗?(请勿在运行时使用解决方案!)

  • 提问者出于好奇而问(使用预处理器宏/常量很容易,但模板参数怎么样?)
  • 意思是:在编译时生成的字符串字面值。我现在认识到const并不强制执行此操作,但可以使用任何在运行时评估的函数进行字符串生成。

1
这根本不可能实现。你试图通过这做到什么? - Moo-Juice
1
@Moo:好奇心,目前没有用途。如果 i 是一个预处理器常量,那就很容易了,但是使用模板我想不到解决方法。 - Thomas
@Reno:使用模板,因为可能会有多个实例化的不同 i;如果没有结构体,我该如何重写它?我认为 template < int i > const char* s = ...; 不会编译。 - Thomas
8个回答

6

从技术上讲是可能的,只是非常丑陋。以下是一个示例,它生成一个无符号整数的字符串字面量。它尚未创建形如“1 2 3 ... i-1”的字符串,但如果你愿意付出努力,我相信这是可能的。

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

OP的问题需要一个大小至少与模板参数值成比例的数组。我认为在静态初始化阶段使用可移植标准C++实现这一点是不可能的(虽然在动态初始化阶段很简单)。但我可能错了... :-) 乾杯! - Cheers and hth. - Alf
在C++0x中,对于静态初始化阶段来说这是微不足道的。太遗憾了,我们还停留在2010年! - Johannes Schaub - litb

3
不,但这是可能的:

不过,这是有可能实现的:

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

顺便问一下,为什么你在使用C字符串?C++有std::string类,它更好。

编辑

我猜你可以使用模板特化:

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

当然,在运行时找到一个解决方案很容易,但这不是我的问题所在;重点是“const”。 - Thomas
当然,我可以使用字符串常量初始化std::string,但首先它必须以某种方式生成! - Thomas
除非进行递归(但是现在我还没有看到递归的方法),否则模板特化将无法提供通用解决方案。 - Thomas
1
如果您使用字符串常量初始化std::string,则可以利用编译时静态特性的所有优势。 后者最重要的好处是它们不存在静态初始化烦恼。 - Johannes Schaub - litb
@Johannes Schaub - litb 我为此避免使用类静态成员,通常如果我需要这样的东西,我会创建一个返回引用的静态函数,就像这样 struct A{ static int& a(){ static int v=0; return v;} }; - BЈовић

1

你所呈现的代码...

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

... 是无效的t::s 必须具有外部链接性。另外,定义需要用模板化。

修复代码中的直接问题,例如...

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

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

... 然后使用任何所需的字符串初始化 T<i>::s 就是微不足道的。

因此,除了代码中的错误之外,答案是:“是的,它不仅可能,而且微不足道。”

但是,为什么您要使用这个鲁伯·戈尔德堡方案来完成微不足道的事情呢?


@Alf:我问这个问题的想法是在编译时生成字符串(这就是为什么我称它为“字面量”的原因)-我的错误是假设 const 可以强制执行此操作。 - Thomas
2
@Vlad:抱歉,你所写的内容都没有意义。这是垃圾。 - Cheers and hth. - Alf
@Alf:只需尝试在“=”后面制作“任何函数调用”的示例,您就会自己看到问题。:-P例如,您可以尝试实现OP实际要求的内容。 - Vlad
1
@Vlad:请停止发表无意义的评论。你迄今所写的一切都毫无意义。你所说的不可能的事情是微不足道的;你提出的推理是无效的;等等,这些都是垃圾。 - Cheers and hth. - Alf
1
@Vlad:请停止发布无意义的评论、无意义的声明和无意义的挑战。我不知道你到底想要什么,是想炫耀还是想占上风,或者是让别人替你做功课,但我不会这样做。我只是礼貌地请求你停止散布无意义和错误信息。谢谢。TIA. - Cheers and hth. - Alf
显示剩余8条评论

1

我认为使用可变参数模板可能是可行的。虽然我没有编译器进行测试,但我想这样的代码可能会起作用。

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

1

不可能实现。

这是因为模板的扩展是在编译时进行的,而编译器只能处理它知道的常量值。任何涉及内存分配的操作(例如初始化字符串)都不可能在此时进行,而只能在运行时进行。


但编译器知道常量模板值,不需要(动态)内存分配。 - Thomas
但它不知道 char s 的长度,必须在内存中生成*。这就是为什么字符串不允许作为模板参数的原因。 - t.g.

0
这是使用模板不可能实现的。但是使用 stringstream,创建此类 string 很容易。以下是伪代码:
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
}

关于stringstream的更多信息可以在这里找到


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

0

现代的C++现在已经可以实现这个功能。

我相信可以使用C++17来实现,但是这个解决方案使用了一些C++20的特性:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接