在编译时将两个常量字符串(或数组)合并为一个常量字符串(或数组)

发布于 2024-09-07 17:55:12 字数 477 浏览 6 评论 0原文

在 C# 和 Java 中,可以使用一个或多个其他常量字符串创建常量字符串。我试图在 C++ 中实现相同的结果(具体来说,在 C++0x 中),但不知道我将使用什么语法来实现它,如果这样的事情在 C++ 中是可能的。这是一个说明我想要做什么的示例:

#include <stdio.h>

const char array1[] = "Hello ";
const char array2[] = "world!\n";
const char array3[] = array1 + array2; // C++ doesn't like it when I try this

int main() {

    printf(array3);

    return 0;

}

有任何指示吗? (没有双关语。)

编辑:我也需要能够将其应用于整数数组 - 不仅仅是字符数组。然而,在这两种情况下,要组合的数组都是固定大小的并且是编译时常量。

In C# and Java, it's possible to create constant strings using one or more other constant strings. I'm trying to achieve the same result in C++ (actually, in C++0x, to be specific), but have no idea what syntax I would use to achieve it, if such a thing is possible in C++. Here's an example illustrating what I want to do:

#include <stdio.h>

const char array1[] = "Hello ";
const char array2[] = "world!\n";
const char array3[] = array1 + array2; // C++ doesn't like it when I try this

int main() {

    printf(array3);

    return 0;

}

Any pointers? (No pun intended.)

EDIT: I need to be able to apply this to integer arrays as well - not just char arrays. However, in both cases, the to-be-combined arrays will be fixed-size and be compile-time constants.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

痴者 2024-09-14 17:55:12

所以......

你不想进行运行时串联。

您不想使用预处理器。

您想要使用常量和输出常量。

好的。但你不会喜欢它:

#include <boost/mpl/string.hpp>

#include <iostream>

int main()
{
  using namespace boost::mpl;

  typedef string<'Hell', 'o '> hello;
  typedef string<'Worl', 'd!'> world;
  typedef insert_range<hello, end<hello>::type, world>::type hello_world;

  std::cout << c_str<hello_world>::value << std::endl;

  std::cin.get();
}

So...

You don't want to do run time concatenation.

You don't want to use the preprocessor.

You want to work with constants and output constants.

OK. But you're not going to like it:

#include <boost/mpl/string.hpp>

#include <iostream>

int main()
{
  using namespace boost::mpl;

  typedef string<'Hell', 'o '> hello;
  typedef string<'Worl', 'd!'> world;
  typedef insert_range<hello, end<hello>::type, world>::type hello_world;

  std::cout << c_str<hello_world>::value << std::endl;

  std::cin.get();
}
み青杉依旧 2024-09-14 17:55:12

使用字符串对象:

#include <iostream>
#include <string>

const std::string s1 = "Hello ";
const std::string s2 = "world!\n";
const std::string s3 = s1 + s2;

int main()
{
  std::cout << s3 << std::endl;
}

Use a string object:

#include <iostream>
#include <string>

const std::string s1 = "Hello ";
const std::string s2 = "world!\n";
const std::string s3 = s1 + s2;

int main()
{
  std::cout << s3 << std::endl;
}
关于从前 2024-09-14 17:55:12

在 C++0x 中,您可以执行以下操作:

template<class Container>
Container add(Container const & v1, Container const & v2){
   Container retval;
   std::copy(v1.begin(),v1.end(),std::back_inserter(retval));
   std::copy(v2.begin(),v2.end(),std::back_inserter(retval));
   return retval;
}

const std::vector<int> v1 = {1,2,3};
const std::vector<int> v2 = {4,5,6};
const std::vector<int> v3 = add(v1,v2);

我认为对于 C++98 中的 STL 容器没有任何方法可以执行此操作(您可以执行 v3 的附加部分,但您可以在 C++98 中不要使用 v1v2 的初始化列表),并且我认为没有任何方法可以对 C++ 中的原始数组执行此操作0x 或 C++98。

In C++0x you can do the following:

template<class Container>
Container add(Container const & v1, Container const & v2){
   Container retval;
   std::copy(v1.begin(),v1.end(),std::back_inserter(retval));
   std::copy(v2.begin(),v2.end(),std::back_inserter(retval));
   return retval;
}

const std::vector<int> v1 = {1,2,3};
const std::vector<int> v2 = {4,5,6};
const std::vector<int> v3 = add(v1,v2);

I don't think there's any way to do this for STL containers in C++98 (the addition part for v3 you can do, but you can't use the initializer lists for v1 and v2 in C++98), and I don't think there's any way to do this for raw arrays in C++0x or C++98.

还给你自由 2024-09-14 17:55:12

在这种情况下,预处理器通常会很方便

#define ARRAY1 "Hello "
#define ARRAY2 "world!\n"

const char array1[] = ARRAY1;
const char array2[] = ARRAY2;
const char array3[] = ARRAY1 ARRAY2;

注意:不需要 +

In cases like this preprocessor often comes handy

#define ARRAY1 "Hello "
#define ARRAY2 "world!\n"

const char array1[] = ARRAY1;
const char array2[] = ARRAY2;
const char array3[] = ARRAY1 ARRAY2;

Note: no + necessary.

说不完的你爱 2024-09-14 17:55:12

我看了你的问题,发现没有人真正回答你的问题。我花了一整天的时间来回答这个问题,但无论如何我的工作都需要它。

我的工作需要 constexpr,我写下答案时就考虑到了这一点。你的工作没有具体说明这一点,但这不会有什么坏处。

什么不起作用;即我的第一次尝试

(我在一台 10 年前的 PowerPC Mac 上使用来自 MacPorts 的 GCC-4.7。)

您可以轻松地将 (C++11) 可变参数函数参数列表转换为任何类型的元组:

template < typename Destination, typename ...Source >
constexpr
auto  initialize( Source&& ...args ) -> Destination
{ return Destination{ce_forward<Source>(args)...}; }

(< code>ce_forward 函数模板就像 std::forward 一样,只是我明确地将其设为 constexpr。)

(当我没有输入 Destination 时 在正文中,我的编译器给出了与无法使用 std::initialization_list 初始化目标相关的错误,因此我现在的表单应该适用于任何聚合或构造函数。形成目标类型支持的形式。)

但是我们需要首先走另一条路,然后使用我上面的代码翻译回来。我尝试了这样的代码:(

template < typename Destination, typename Source, typename Size1, typename Size2, typename ...Args >
constexpr
auto  fill_from_array( Source&& source, Size1 index_begin, Size2 index_end, Args&& ...args )
 -> Destination
{
    return ( index_begin < index_end )
      ? fill_from_array<Destination>( ce_forward<Source>(source), index_begin + 1, index_end, ce_forward<Args>(args)..., ce_forward<Source>(source)[index_begin] )
      : initialize<Destination>( ce_forward<Args>(args)... );
}

因为我也需要两个源,所以我制作了这个函数的更大版本。)

当我实际运行这段代码时,我的计算机在一小时后因超出虚拟内存而崩溃。我猜这会导致无限循环或其他原因。 (或者也许它是有限的,但对于我古老的系统来说太多了。)

我的第二次尝试

是这样搜索的,直到找到可能有用的东西:

并从这些内容和我读到的其他内容中拼凑出一个解决方案:在编译时解析字符串 - 第一部分 。基本上,我们使用上面的 initialize 函数模板的变体;我们不使用纯粹基于函数可变参数的初始化器,而是使用模板可变参数的映射。

最简单的映射源是非负整数:

#include <cstddef>

template < std::size_t ...Indices >
struct index_tuple
{ using next = index_tuple<Indices..., sizeof...(Indices)>; };

template < std::size_t Size >
struct build_indices
{ using type = typename build_indices<Size - 1>::type::next; };

template < >
struct build_indices< 0 >
{ using type = index_tuple<>; };

index_tuple 类模板是我们将传递用于映射的内容,而 build_indices 类模板则将 index_tuple< /code> 以正确的格式实例化:

index_tuple<>
index_tuple<0>
index_tuple<0, 1>
index_tuple<0, 1, 2>
...

我们使用函数模板创建 index_tuple 对象:

template < std::size_t Size >
constexpr
auto  make_indices() noexcept -> typename build_indices<Size>::type
{ return {}; }

我们使用所述 index_tuple 对象来贡献函数模板的模板头:

#include <array>

template < std::size_t N, std::size_t M, std::size_t ...Indices >
constexpr
std::array<char, N + M - 1u>
fuse_strings_impl( const char (&f)[N], const char (&s)[M], index_tuple<Indices...> );

第三个参数没有名称,因为我们不需要该对象本身。我们只需要标头中的“std::size_t ...Indices”。我们知道展开后会变成“0,1,...,X”。我们会将有序扩展提供给函数调用,该函数调用将扩展为所需的初始值设定项。作为示例,让我们看一下上面函数的定义:

template < std::size_t N, std::size_t M, std::size_t ...Indices >
constexpr
std::array<char, N + M - 1u>
fuse_strings_impl( const char (&f)[N], const char (&s)[M], index_tuple<Indices...> )
{ return {{ get_strchr<Indices>(f, s)... }}; }

我们将返回一个 array,其中第一个元素为 get_strchr<0>(f,s),第二个为 get_strchr<1>(f,s),依此类推。请注意,此函数名称以“_impl”结尾,因为我隐藏了 index_tuple 的使用,并通过调用公共版本来确保正确的基本情况:

template < std::size_t N, std::size_t M >
constexpr
std::array<char, N + M - 1u>
fuse_strings( const char (&f)[N], const char (&s)[M] )
{ return fuse_strings_impl(f, s, make_indices<N + M - 2>()); }

并且您可以尝试这样编码:

#include <iostream>
#include <ostream>

int  main()
{
    using std::cout;
    using std::endl;

    constexpr auto  initialize_test = initialize<std::array<char, 15>>( 'G',
     'o', 'o', 'd', 'b', 'y', 'e', ',', ' ', 'm', 'o', 'o', 'n', '!', '\0' );
    constexpr char  hello_str[] = "Hello ";
    constexpr char  world_str[] = "world!";
    constexpr auto  hw = fuse_strings( hello_str, world_str );

    cout << initialize_test.data() << endl;
    cout << hw.data() << endl;
}

有一些微妙之处留意。

  • 您的 const(expr) char str[] = "Whatever"; 声明必须使用 [] 而不是 * ,以便编译器识别您的对象作为内置数组,而不是作为指向运行时长度的未知(固定)内存的指针。
  • 由于内置数组不能用作返回类型,因此必须使用 std::array 作为替代。问题是当您必须将结果用于以后的合并时。 std::array 对象必须具有适当的内置数组类型的公开可用的非静态数据成员,但该成员的名称未在标准中指定,也可能没有指定持续的。因此,您必须创建一个类似的 std::array 工作才能正式避免黑客攻击。
  • 您不能对一般数组连接和字符串连接使用相同的代码。字符串是 char 数组,其中最后一个元素必须是 '\0'。从字符串读取时必须跳过这些 NUL 值,但在写入字符串时添加这些值。这就是我的代码中出现奇数 1 和 2 的原因。对于一般数组连接(包括非字符串char),必须读取每个数组中的每个元素,并且不应将额外的元素添加到组合数组中。

哦,这是 get_strchr 的定义:(

template < std::size_t N >
constexpr
char  ce_strchr( std::size_t i, const char (&s)[N] )
{
    static_assert( N, "empty string" );
    return (i < ( N - 1 )) ? s[i] : throw "too big";
}

template < std::size_t N, std::size_t M, std::size_t ...L >
constexpr
char  ce_strchr( std::size_t i, const char (&f)[N], const char (&s)[M], const char (&...t)[L] )
{
    static_assert( N, "empty string" );
    return (i < ( N - 1 )) ? f[i] : ce_strchr(i + 1 - N, s, t...);
}

template < std::size_t I, std::size_t N, std::size_t ...M >
constexpr
char  get_strchr( const char (&f)[N], const char (&...s)[M] )
{ return ce_strchr(I, f, s...); }

我希望你能读到这篇文章。)

I took a look at your question, and noticed that no one really answered it. My diversion to answer it took all day to perfect, but I needed it anyway for my work.

My work needs constexpr, and I wrote the answer with that in mind. Your work doesn't specify that, but it can't hurt.

What didn't work; i.e. my first try

(I'm using GCC-4.7, from MacPorts, on a 10-year old PowerPC Mac.)

You can easily turn a (C++11) variadic function parameter list to any kind of tuple:

template < typename Destination, typename ...Source >
constexpr
auto  initialize( Source&& ...args ) -> Destination
{ return Destination{ce_forward<Source>(args)...}; }

(The ce_forward function template is just like std::forward, except I explicitly made it constexpr.)

(When I didn't put Destination in the body, my compiler gave me errors relating to the destination not being able to be initialized with a std::initialization_list; so the form I have now should work with any aggregate or constructor form the destination type supports.)

But we need to initially go the other way, then use my code above to translate back. I tried code like this:

template < typename Destination, typename Source, typename Size1, typename Size2, typename ...Args >
constexpr
auto  fill_from_array( Source&& source, Size1 index_begin, Size2 index_end, Args&& ...args )
 -> Destination
{
    return ( index_begin < index_end )
      ? fill_from_array<Destination>( ce_forward<Source>(source), index_begin + 1, index_end, ce_forward<Args>(args)..., ce_forward<Source>(source)[index_begin] )
      : initialize<Destination>( ce_forward<Args>(args)... );
}

(Since I needed two sources too, I made a bigger version of this function.)

When I actually ran this code, my computer crapped out after an hour from exceeding virtual memory. I guess this causes an infinite loop or something. (Or maybe it's finite, but too much for my ancient system.)

My second try

I scoured S.O. until I found stuff that could be useful:

and pieced a solution from those and something else I read: Parsing strings at compile-time — Part I. Basically, we use a variant of my initialize function template above; instead of basing the initializers purely off function variadic parameters, we use a mapping from template variadic parameters.

The easiest mapping source is the nonnegative integers:

#include <cstddef>

template < std::size_t ...Indices >
struct index_tuple
{ using next = index_tuple<Indices..., sizeof...(Indices)>; };

template < std::size_t Size >
struct build_indices
{ using type = typename build_indices<Size - 1>::type::next; };

template < >
struct build_indices< 0 >
{ using type = index_tuple<>; };

The index_tuple class template is what we'll be passing around for mapping, while the build_indices class template puts the index_tuple instantiations in the right format:

index_tuple<>
index_tuple<0>
index_tuple<0, 1>
index_tuple<0, 1, 2>
...

We create index_tuple objects with a function template:

template < std::size_t Size >
constexpr
auto  make_indices() noexcept -> typename build_indices<Size>::type
{ return {}; }

We use said index_tuple objects for their contribution to a function template's template header:

#include <array>

template < std::size_t N, std::size_t M, std::size_t ...Indices >
constexpr
std::array<char, N + M - 1u>
fuse_strings_impl( const char (&f)[N], const char (&s)[M], index_tuple<Indices...> );

The third parameter doesn't get a name because we won't need the object itself. We just need the "std::size_t ...Indices" in the header. We know that will turn into a "0, 1, ..., X" when expanded. We'll feed that orderly expansion into a function call that gets expanded into the required initializers. As an example, let's look at the definition of the function above:

template < std::size_t N, std::size_t M, std::size_t ...Indices >
constexpr
std::array<char, N + M - 1u>
fuse_strings_impl( const char (&f)[N], const char (&s)[M], index_tuple<Indices...> )
{ return {{ get_strchr<Indices>(f, s)... }}; }

We'll be returning an array with the first element as get_strchr<0>(f,s), the second as get_strchr<1>(f,s), and so on. Note that this function name ends with "_impl" because I hide the use of index_tuple and ensure a proper base case by calling the public version:

template < std::size_t N, std::size_t M >
constexpr
std::array<char, N + M - 1u>
fuse_strings( const char (&f)[N], const char (&s)[M] )
{ return fuse_strings_impl(f, s, make_indices<N + M - 2>()); }

And you can try to code as so:

#include <iostream>
#include <ostream>

int  main()
{
    using std::cout;
    using std::endl;

    constexpr auto  initialize_test = initialize<std::array<char, 15>>( 'G',
     'o', 'o', 'd', 'b', 'y', 'e', ',', ' ', 'm', 'o', 'o', 'n', '!', '\0' );
    constexpr char  hello_str[] = "Hello ";
    constexpr char  world_str[] = "world!";
    constexpr auto  hw = fuse_strings( hello_str, world_str );

    cout << initialize_test.data() << endl;
    cout << hw.data() << endl;
}

There are some subtleties to watch for.

  • Your declarations of const(expr) char str[] = "Whatever"; have to use [] instead of * so the compiler recognizes your object as a built-in array, and not as a pointer to unknown (fixed) memory of run-time length.
  • Since built-in arrays can't be used as return types, you have to use std::array as a substitute. The problem is when you have to use the result for a later merging. A std::array object has to have a publicly available non-static data member of the appropriate built-in array type, but the name of that member is not specified in the standard and probably isn't consistent. So you have to create a std::array work-alike to officially avoid hacks.
  • You can't use the same code for general array joins and string joins. A string is a char array where the last element must be '\0'. Those NUL values must be skipped when reading from strings but added when writing one. That why the odd 1's and 2's appear in my code. For general array joins (including non-string char ones), every element in every array must be read, and no extra elements should be added to the combined array.

Oh, here's the definition of get_strchr:

template < std::size_t N >
constexpr
char  ce_strchr( std::size_t i, const char (&s)[N] )
{
    static_assert( N, "empty string" );
    return (i < ( N - 1 )) ? s[i] : throw "too big";
}

template < std::size_t N, std::size_t M, std::size_t ...L >
constexpr
char  ce_strchr( std::size_t i, const char (&f)[N], const char (&s)[M], const char (&...t)[L] )
{
    static_assert( N, "empty string" );
    return (i < ( N - 1 )) ? f[i] : ce_strchr(i + 1 - N, s, t...);
}

template < std::size_t I, std::size_t N, std::size_t ...M >
constexpr
char  get_strchr( const char (&f)[N], const char (&...s)[M] )
{ return ce_strchr(I, f, s...); }

(I hope you get to read this.)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文