【问题标题】:Combine two constant strings (or arrays) into one constant string (or array) at compile time在编译时将两个常量字符串(或数组)组合成一个常量字符串(或数组)
【发布时间】:2011-03-10 09:51:15
【问题描述】:

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

}

有什么建议吗? (没有双关语。)

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

【问题讨论】:

  • 等等,谁说过在 Java/C# 的编译时评估字符串文字的连接?这对我来说听起来不正确。
  • 反汇编生成的 MSIL(在 C# 中)将显示它们是。目前我没有方便的 Java 反汇编程序,但我很确定在 Java 中也是如此。

标签: c++ arrays constants c++11 concatenation


【解决方案1】:

所以...

您不想进行运行时连接。

你不想使用预处理器。

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

好的。但你不会喜欢它:

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

【讨论】:

  • 整数版本类似但使用vector_c
【解决方案2】:

使用字符串对象:

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

【讨论】:

  • 您需要使这些 const 等同于 OP。
  • 是的,我在原始帖子后大约 5 秒意识到了这一点。现已修复。
  • 我还需要使用整数数组。我会在运行时使用连接(std::string 会这样做)作为最后的手段,但如果可能的话,我更愿意使用编译时常量。
【解决方案3】:

在 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 的附加部分你可以这样做,但你不能使用 v1 和 @987654324 的初始化列表@ 在 C++98 中),我认为对于 C++0x 或 C++98 中的原始数组没有任何方法。

【讨论】:

  • 谢谢。如果有一个编译时解决方案,我仍然坚持,但如果没有,我可能会使用向量或动态数组。
  • 在 C++1x 中,这应该使用 constexpr 而不是 const
  • @sbi 这仅通过添加 constexpr 不起作用,此外,该关键字不适用于函数参数。 constexpr 函数只能有一个返回表达式语句,该语句本身必须是一个常量表达式。在 constexpr 中循环的唯一机制是通过递归。
  • @snk_kid:听起来你是对的。 (我希望我有时间使用 C++11 编译器。)
【解决方案4】:

在这种情况下,预处理器通常会派上用场

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

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

注意:不需要+

【讨论】:

  • 预处理器是个好东西,但我特别需要使用 const 的。我正在使用可变参数模板,虽然数组的大小和内容将在编译时知道,但我不知道为它们创建宏的任何方法。
  • 如果你想要编译时连接,这是正确的方法
  • @nonoitall:在这种情况下,您正在使用 const。预处理器仅用于统一初始化这些常量。从上面的代码示例中这不是很明显吗?
  • 预处理器需要一些可以使用的东西——我不能给它的东西。我正在创建一个具有可变数量参数的模板类,并且该模板具有一个静态数组,其元素数量与模板具有参数的数量相同。这些元素将被初始化为每个模板的自定义成员所在的类中的偏移量。我知道没有办法将此信息传递给预处理器,尽管所有这些信息在编译时都是已知的。
【解决方案5】:

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

我的工作需要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)...}; }

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

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

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

我的第二次尝试

我搜索了 S.O.直到我找到有用的东西:

并从我读到的这些和其他内容中拼凑出一个解决方案:Parsing strings at compile-time — Part I。基本上,我们使用上面我的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 实例化以正确的格式:

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&lt;0&gt;(f,s),第二个元素为get_strchr&lt;1&gt;(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 work-alike 来正式避免黑客攻击。
  • 一般数组连接和字符串连接不能使用相同的代码。字符串是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...); }

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-05-03
    • 2010-12-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-13
    • 2017-06-08
    • 2011-04-30
    相关资源
    最近更新 更多