当前位置: 首页 > 知识库问答 >
问题:

在C语言中方便地声明编译时字符串

林魁
2023-03-14

能够在C语言编译期间创建和操作字符串有几个有用的应用。虽然在C语言中创建编译时字符串是可能的,但是这个过程非常麻烦,因为字符串需要声明为一个可变的字符序列,例如。

using str = sequence<'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!'>;

字符串串联、子串提取等操作可以很容易地实现为对字符序列的操作。是否可以更方便地声明编译时字符串?如果没有,工作中是否有允许方便地声明编译时字符串的建议?

理想情况下,我们希望能够声明编译时字符串,如下所示:

// Approach 1
using str1 = sequence<"Hello, world!">;

或者,使用用户定义的文字,

// Approach 2
constexpr auto str2 = "Hello, world!"_s;

其中dectype(str2)将有一个Constexpr构造函数。利用您可以执行以下操作的事实,可以实现方法1的更混乱版本:

template <unsigned Size, const char Array[Size]>
struct foo;

然而,数组需要有外部链接,所以为了让方法1工作,我们必须编写这样的内容:

/* Implementation of array to sequence goes here. */

constexpr const char str[] = "Hello, world!";

int main()
{
    using s = string<13, str>;
    return 0;
}

不用说,这很不方便。方法2实际上不可能实现。如果我们要声明(constexpr)文本运算符,那么我们将如何指定返回类型?由于我们需要运算符返回可变字符序列,因此我们需要使用const char*参数来指定返回类型:

constexpr auto
operator"" _s(const char* s, size_t n) -> /* Some metafunction using `s` */

这会导致编译错误,因为s不是Constexpr。试图通过做以下事情来解决这个问题并没有多大帮助。

template <char... Ts>
constexpr sequence<Ts...> operator"" _s() { return {}; }

该标准规定,这种特定的文本运算符形式是为整数和浮点类型保留的。虽然123\u可以工作,但abc\u不行。如果我们完全抛弃用户定义的文本,只使用常规的constexpr函数,会怎么样?

template <unsigned Size>
constexpr auto
string(const char (&array)[Size]) -> /* Some metafunction using `array` */

与前面一样,我们遇到了这样一个问题:数组(现在是constexpr函数的参数)本身不再是constexpr类型。

我相信应该可以定义一个C预处理器宏,它将字符串和字符串的大小作为参数,并返回由字符串中的字符组成的序列(使用BOOST_PP_FOR、Stringiation、数组下标等)。然而,我没有时间(或足够的兴趣)来实现这样一个宏=)

共有3个答案

华项明
2023-03-14

编辑:正如Howard Hinnant(我在对OP的评论中也提到了这一点)所指出的,您可能不需要将字符串的每个字符都作为一个模板参数的类型。如果您确实需要,下面有一个无宏的解决方案。

我在编译时尝试使用字符串时发现了一个技巧。它需要引入“模板字符串”之外的另一种类型,但在函数中,可以限制此类型的范围。

它不使用宏,而是一些C11特性。

#include <iostream>

// helper function
constexpr unsigned c_strlen( char const* str, unsigned count = 0 )
{
    return ('\0' == str[0]) ? count : c_strlen(str+1, count+1);
}

// destination "template string" type
template < char... chars >
struct exploded_string
{
    static void print()
    {
        char const str[] = { chars... };
        std::cout.write(str, sizeof(str));
    }
};

// struct to explode a `char const*` to an `exploded_string` type
template < typename StrProvider, unsigned len, char... chars  >
struct explode_impl
{
    using result =
        typename explode_impl < StrProvider, len-1,
                                StrProvider::str()[len-1],
                                chars... > :: result;
};

    // recursion end
    template < typename StrProvider, char... chars >
    struct explode_impl < StrProvider, 0, chars... >
    {
         using result = exploded_string < chars... >;
    };

// syntactical sugar
template < typename StrProvider >
using explode =
    typename explode_impl < StrProvider,
                            c_strlen(StrProvider::str()) > :: result;


int main()
{
    // the trick is to introduce a type which provides the string, rather than
    // storing the string itself
    struct my_str_provider
    {
        constexpr static char const* str() { return "hello world"; }
    };
    
    auto my_str = explode < my_str_provider >{};    // as a variable
    using My_Str = explode < my_str_provider >;    // as a type
    
    my_str.print();
}
 
岳毅
2023-03-14

我认为应该可以定义一个C预处理器宏,该宏以字符串和字符串的大小作为参数,并返回由字符串中的字符组成的序列(使用BOOST_PP_FOR、字符串化、数组下标等)。但是,我没有时间(或足够的兴趣)实现这样一个宏

它可以实现这一点,而不依赖于升压,使用非常简单的宏和一些C11功能

  1. lambdas可变的

(此处不严格要求后两者)

>

  • 我们需要能够使用用户提供的从0到N的标记实例化可变模板-例如,一个工具也很有用,可以将元组扩展到可变模板函数的参数中(请参阅问题:如何将元组扩展到可变模板函数的参数中?
    “解包”元组以调用匹配的函数指针)

    namespace  variadic_toolbox
    {
        template<unsigned  count, 
            template<unsigned...> class  meta_functor, unsigned...  indices>
        struct  apply_range
        {
            typedef  typename apply_range<count-1, meta_functor, count-1, indices...>::result  result;
        };
    
        template<template<unsigned...> class  meta_functor, unsigned...  indices>
        struct  apply_range<0, meta_functor, indices...>
        {
            typedef  typename meta_functor<indices...>::result  result;
        };
    }
    

    然后定义一个变量模板,称为字符串,带有非类型参数char:

    namespace  compile_time
    {
        template<char...  str>
        struct  string
        {
            static  constexpr  const char  chars[sizeof...(str)+1] = {str..., '\0'};
        };
    
        template<char...  str>
        constexpr  const char  string<str...>::chars[sizeof...(str)+1];
    }
    

    现在最有趣的部分是将字符文本传递到字符串模板:

    namespace  compile_time
    {
        template<typename  lambda_str_type>
        struct  string_builder
        {
            template<unsigned... indices>
            struct  produce
            {
                typedef  string<lambda_str_type{}.chars[indices]...>  result;
            };
        };
    }
    
    #define  CSTRING(string_literal)                                                        \
        []{                                                                                 \
            struct  constexpr_string_type { const char * chars = string_literal; };         \
            return  variadic_toolbox::apply_range<sizeof(string_literal)-1,                 \
                compile_time::string_builder<constexpr_string_type>::produce>::result{};    \
        }()
    

    一个简单的级联演示显示了用法:

        namespace  compile_time
        {
            template<char...  str0, char...  str1>
            string<str0..., str1...>  operator*(string<str0...>, string<str1...>)
            {
                return  {};
            }
        }
    
        int main()
        {
            auto  str0 = CSTRING("hello");
            auto  str1 = CSTRING(" world");
    
            std::cout << "runtime concat: " <<  str_hello.chars  << str_world.chars  << "\n <=> \n";
            std::cout << "compile concat: " <<  (str_hello * str_world).chars  <<  std::endl;
        }
    

    https://ideone.com/8Ft2xu

  • 宇文良骏
    2023-03-14

    我还没有看到任何东西能与斯科特·舒尔在2012年《现在》上展示的《代码str_const》的优雅相媲美。不过,它确实需要Constrexr

    以下是您可以如何使用它,以及它可以做什么:

    int
    main()
    {
        constexpr str_const my_string = "Hello, world!";
        static_assert(my_string.size() == 13, "");
        static_assert(my_string[4] == 'o', "");
        constexpr str_const my_other_string = my_string;
        static_assert(my_string == my_other_string, "");
        constexpr str_const world(my_string, 7, 5);
        static_assert(world == "world", "");
    //  constexpr char x = world[5]; // Does not compile because index is out of range!
    }
    

    它不会比编译时范围检查更酷!

    使用和实现都不需要宏。并且没有人为限制字符串大小。我想在这里发布实现,但我尊重斯科特的隐含版权。实现在他的演示文稿的一张幻灯片上,链接到上面。

     类似资料:
    • 我有一个c模块: 我做了File fileName; 我有一个初始化函数: 所以我做了fileName=filename;我这样做的原因是我有另一个函数,我称为start(): 一开始我有文件名,但它没有找到它,所以我想用fileName代替。但我现在得到一些错误: 在这一行:fileName=fileName;在=符号上,我得到红线错误: 错误1错误C2440:“=”:无法从“常量字符*”转换为

    • 问题内容: 我正在尝试从C创建一个go字符串。我有指针和长度,所以如果我从go开始,可以调用该函数。 生成结构,所以我想知道是否可以直接使用它: 我在这里用它来控制我的生命。在随后作为参数传递给函数去: Go的垃圾收集器会尝试回收内存吗? 问题答案: Go的垃圾回收器不会尝试回收使用C内存分配器分配的内存。您所描述的应该是安全的。当然,您可能无法释放C内存,因为您不知道Go将在何时完成。

    • 主要内容:#if 的用法,#ifdef 的用法,#ifndef 的用法,三者之间的区别假如现在要开发一个C语言程序,让它输出红色的文字,并且要求跨平台,在 Windows 和 Linux 下都能运行,怎么办呢? 这个程序的难点在于,不同平台下控制文字颜色的代码不一样,我们必须要能够识别出不同的平台。 Windows 有专有的宏 ,Linux 有专有的宏 ,以现有的知识,我们很容易就想到了 if else,请看下面的代码: 但这段代码是错误的,在 Windows 下提示 __linu

    • 上一个小节 C 语言的程序结构中我们简单的认识了一下 C 语言的程序到底由那些部分组成,但是仅仅了解 C 语言的程序结构还不够,仅仅是万里长征走完了第一步,后面还需要进行很多的步骤才能让程序成为一个可以使用的工业产品。还记的上一个小节中我们提到的 gcc 吗?其实这是一个 C 语言的编译器,这一个小节我们就来学习一下编译器这个概念。 1. C 语言编译器 每个语言都需要编译器,甚至包括可以直接操作

    • 问题内容: 我正在尝试更好地理解它们之间的区别。我在网上找到了很多解释,但是它们倾向于抽象的差异,而不是实际的含义。 我的大部分编程经验都来自CPython(动态的,解释的)和Java(静态的,编译的)。但是,我知道还有其他种类的解释和编译语言。除了可以从以编译语言编写的程序中分发可执行文件这一事实之外,每种类型是否有优点/缺点?通常,我听到人们争辩说解释语言可以交互使用,但是我相信编译语言也可以

    • 所以我将一个字符串传递到main函数:int main(int argc,char*argv[]) 我理解argc(在这种情况下是2),但是不明白我如何逐个字符地阅读argv[]?当我打印argv[0]不应该打印该字符串的字符数组中的第一个字符吗? 谢啦