简体   繁体   English

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

[英]Combine two constant strings (or arrays) into one constant string (or array) at compile time

In C# and Java, it's possible to create constant strings using one or more other constant strings. 在C#和Java中,可以使用一个或多个其他常量字符串创建常量字符串。 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++. 我试图在C ++中实现相同的结果(实际上,在C ++ 0x中,具体而言),但是不知道我将使用什么语法来实现它,如果在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. 编辑:我需要能够将它应用于整数数组 - 而不仅仅是char数组。 However, in both cases, the to-be-combined arrays will be fixed-size and be compile-time constants. 但是,在这两种情况下,待组合数组都是固定大小的并且是编译时常量。

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

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. 我的工作需要constexpr ,我写下了答案。 Your work doesn't specify that, but it can't hurt. 你的工作没有指明,但它不会伤害。

What didn't work; 什么都行不通; ie my first try 即我的第一次尝试

(I'm using GCC-4.7, from MacPorts, on a 10-year old PowerPC Mac.) (我在MacPorts上使用GCC-4.7,在一台已有10年历史的PowerPC Mac上。)

You can easily turn a (C++11) variadic function parameter list to any kind of tuple: 您可以轻松地将(C ++ 11)可变参数函数参数列表转换为任何类型的元组:

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 .) ce_forward函数模板就像std::forward ,除了我明确地使它成为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.) (当我没有将Destination放入正文时,我的编译器给了我与目标无法使用std::initialization_list ;因此我现在的表单应该与目标的任何聚合或构造函数一起使用类型支持。)

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 SO until I found stuff that could be useful: 我搜索了SO,直到找到可能有用的东西:

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; 基本上,我们使用上面的initialize函数模板的变体; 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类模板是什么,我们将通过周围的映射,而build_indices类模板放index_tuple实例以正确的格式:

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

We create index_tuple objects with a function template: 我们使用函数模板创建index_tuple对象:

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: 我们使用所述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...> );

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. 我们只需要标题中的“std :: size_t ... Indices”。 We know that will turn into a "0, 1, ..., X" when expanded. 我们知道在扩展时会变成“0,1,......,X”。 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. 我们将返回一个array ,其中第一个元素为get_strchr<0>(f,s) ,第二个get_strchr<1>(f,s)get_strchr<1>(f,s) ,依此类推。 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: 请注意,此函数名称以“_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>()); }

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"; 你的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. 由于内置数组不能用作返回类型,因此必须使用std::array作为替代。 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. std::array对象必须具有相应内置数组类型的公共可用非静态数据成员,但该成员的名称未在标准中指定,并且可能不一致。 So you have to create a std::array work-alike to officially avoid hacks. 所以你必须创建一个std::array工作方式来正式避免黑客攻击。
  • 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' . 字符串是char数组,其中最后一个元素必须是'\\0' Those NUL values must be skipped when reading from strings but added when writing one. 从字符串读取时必须跳过那些NUL值,但在写入字符串时添加。 That why the odd 1's and 2's appear in my code. 这就是我的代码中出现奇数1和2的原因。 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. 对于一般数组连接(包括非字符串char连接),必须读取每个数组中的每个元素,并且不应将任何额外元素添加到组合数组中。

Oh, here's the definition of get_strchr : 哦,这是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.) (我希望你能读到这篇文章。)

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

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. 注意:没有+必要。

In C++0x you can do the following: 在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);

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. 我不认为在C ++ 98中有任何方法可以为STL容器执行此操作(您可以执行v3的添加部分,但不能在C ++ 98中使用v1v2的初始化列表),以及我认为在C ++ 0x或C ++ 98中没有任何方法可以对原始数组执行此操作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM