6

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.

nonoitall
  • 1,232
  • 1
  • 15
  • 25
  • Hold on, who ever said concatenation of string literals is evaluated at compile time in Java/C#? That doesn't sound correct to me. – snk_kid Jul 01 '10 at 08:07
  • Disassembling the generated MSIL (in C#) will reveal that they are. I don't have a Java disassembler handy at the moment, but I'm pretty sure it's the same case in Java. – nonoitall Jul 02 '10 at 00:31

5 Answers5

7

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();
}
101
  • 8,514
  • 6
  • 43
  • 69
Edward Strange
  • 40,307
  • 7
  • 73
  • 125
5

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;
}
SCFrench
  • 8,244
  • 2
  • 31
  • 61
  • You'll want to make those const to be equivalent to the OP. – Edward Strange Jun 30 '10 at 23:39
  • Yeah, I realized that about 5 seconds after the original post. Fixed now. – SCFrench Jun 30 '10 at 23:43
  • I'm also going to need to work with integer arrays. I'd use concatenation at runtime (which std::string does) as a last resort, but would prefer to have compile-time constants if at all possible. – nonoitall Jun 30 '10 at 23:46
4

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

Community
  • 1
  • 1
CTMacUser
  • 1,996
  • 1
  • 16
  • 27
4

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.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • The preprocessor is a beautiful thing, but I specifically need to work with const's. I'm using variadic templates, and while the arrays' sizes and contents will be known at compile-time, I don't know any way of creating macros for them. – nonoitall Jun 30 '10 at 23:44
  • If you want a compile time concatenation this is the right way to go – bjg Jun 30 '10 at 23:45
  • @nonoitall: You *are* working with consts in this case. Preprocessor is only used to *initialize* these consts uniformly. Isn't that obvious from the above code sample? – AnT stands with Russia Jun 30 '10 at 23:46
  • The preprocessor needs something to work with though - something I can't give it. I'm creating a template class with a variable number of arguments, and the template has a static array with the same number of elements as the template has parameters. These elements are to be initialized to the offset in the class where each of the template's custom members are located. I know of no way to pass this information to the preprocessor, though all of it is known at compile-time. – nonoitall Jun 30 '10 at 23:51
4

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.

Ken Bloom
  • 57,498
  • 14
  • 111
  • 168
  • Thanks. I'm still holding out for a compile-time solution if one presents itself, but will probably use vectors or dynamic arrays if none does. – nonoitall Jun 30 '10 at 23:58
  • In C++1x, this should use `constexpr` instead of `const`. – sbi Jul 01 '10 at 06:43
  • @sbi this will not work just by adding constexpr, the keyword is not applied to function arguments besides. constexpr functions can only have one return expression statement that has to be a constant expression itself. The only mechanism to loop in a constexpr is via recursion. – snk_kid Jul 01 '10 at 10:11
  • @snk_kid: Sounds like you're right. (I wish I had time to play with a C++11 compiler.) – sbi Jul 01 '10 at 17:14