2

How can I concatenate the following char and TCHAR variables in C++?

TCHAR fileName[50];

TCHAR prefix[5] = "file_";
TCHAR ext[4] = ".csv";
char *id[10];
generateId(*id);

The generateId(char *s) function simply generates a random string. I need to end up with fileName being something like file_randomIdGoesHere.csv

I have tried strncat(fileName, prefix, 5); which works fine with all TCHAR variables but not with char * as it requires a const char * instead, so maybe there's a better way of doing it, not sure how to convert char * or char ** to const char *.

Any ideas?

The error I get with strncat(fileName, id, 10) is error: cannot convert 'char**' to 'const char*'

Mr.C64
  • 41,637
  • 14
  • 86
  • 162
matt
  • 2,312
  • 5
  • 34
  • 57
  • 2
    shouldn't it be "char id[10];" (without asterisk)? – No-Bugs Hare Jun 03 '15 at 09:56
  • It seems to me in your code you should have something like `char id[10];` instead of `char *id[10]`, and then simply use `generateId(id)`. Why did you add another indirection level with `char *id[10]`? – Mr.C64 Jun 03 '15 at 09:56
  • On my `generateId(char *s)` if I remove the * it gives me an error when I try to access each element of the `s` array – matt Jun 03 '15 at 09:58
  • 1
    @matt: Would it be possible and make sense for you to remove the `*` in the array, definition? i.e. just make it `char id[10]`? – Mr.C64 Jun 03 '15 at 10:00
  • @Mr.C64 I have just tried, I also have to remove it from the function argument and then within the function every time I try to access `s[i]` I get the error `error: invalid types 'char[int]' for array subscript` – matt Jun 03 '15 at 10:02
  • You do not have any `char` or `TCHAR` variables here. – Lightness Races in Orbit Jun 03 '15 at 10:11
  • 1
    @Mr.C64 yup, removing the * from `char *id[10];` and `generateId(*id);` actually worked, I was able to concatenate it with `strncat(fileName, id, 10);` thanks! – matt Jun 03 '15 at 10:14
  • @matt: Glad it worked. However, you code is very fragile. For example, if you build it in Unicode (which has been the default since VS2005), your code won't compile. Moreover, unless there is a _strong reason_ to not do that, I consider using a **string class** with convenient overloaded operator+/+= much better in C++ code, than concatenating raw character arrays with C functions like strncat. – Mr.C64 Jun 03 '15 at 10:24
  • @Mr.C64 Yeah that makes sense, although in this particular case I can't use a string class, it's for an embedded system and it has limited resources. But thanks for the tip, I will keep it in mind. – matt Jun 03 '15 at 11:21

4 Answers4

3

The error you are seeing is because your id array is declared wrong. You declared an array of pointers instead of an array of characters. It should be more like this:

char id[10];
generateId(id);

That being said, you are also assigning char-based string literals to your TCHAR arrays, which means you are not compiling your project for Unicode, otherwise such assignments would fail to compile. So you may as well replace TCHAR with char:

char fileName[50] = {0};

char prefix[] = "file_";
char ext[] = ".csv";
char id[10] = {0};

generateId(id);

And then, you should change strncat() to _snprintf():

_snprintf(filename, 49, "%s%s.cvs", prefix, id);

If you really want to use TCHAR then you need to change everything to TCHAR, and use the TEXT() macro for literals:

TCHAR fileName[50] = {0};

TCHAR prefix[] = TEXT("file_");
TCHAR ext[] = TEXT(".csv");
TCHAR id[10] = {0};

generateId(id);
__sntprintf(filename, 49, TEXT("%s%s.cvs"), prefix, id);

If you cannot change id to TCHAR then you will have to perform a runtime conversion:

TCHAR fileName[50] = {0};

TCHAR prefix[] = TEXT("file_");
TCHAR ext[] = TEXT(".csv");
char id[10] = {0};

generateId(id);

#ifdef UNICODE
wchar_t id2[10] = {0};
MultiByteToWideChar(CP_ACP, 0, id, -1, id2, 10);
#else
char *id2 = id;
#endif

__sntprintf(filename, 49, TEXT("%s%s.cvs"), prefix, id2);
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • It would be a bit neater to add "#else #define id2 id" to the last-but-one #ifdef, which would allow to remove last #ifdef completely. – No-Bugs Hare Jun 03 '15 at 10:20
  • But then you have to remember to `#undef id2` afterwards to avoid any possible side-effects. – Remy Lebeau Jun 03 '15 at 10:26
  • ok, you could have it as const TCHAR* id2 = id; instead of define to preserve scoping - or to do as you've change it. – No-Bugs Hare Jun 03 '15 at 10:52
  • "you could have it as const TCHAR* id2 = id;" - no you couldn't, because in the situation where `id2` is being used, `id2` is `wchar_t[]` but `id` is `char[]` instead. That is the whole reason `id2` exists - to facilitate a data conversion from one character type to another type. – Remy Lebeau Jun 03 '15 at 15:53
  • Huh? I'm speaking about introducing id2 when UNICODE is *not* defined, so TCHAR and char are the same (as you yourself have pointed out). No problems with your current code BTW. – No-Bugs Hare Jun 03 '15 at 15:57
  • Welcome to the wide world of `TCHAR` programming! :) – Remy Lebeau Jun 03 '15 at 16:05
2

The first thing you should do is, since you are using C++ and not pure C, just use a string class to represent your strings and to manage them in a way much more convenient than raw C-style character arrays.

In the context of Windows C++ programming, CString is a very convenient string class.
You can use its overloaded operator+ (or +=) to concatenate strings in a convenient, robust and easy way.

If you have an id stored in a char string (as an ASCII string), as you showed in your question's code:

char id[10];  
generateId(id);

you can first create a CString around it (this will also convert from char-string to TCHAR-string, in particular to wchar_t-string if you are using Unicode builds, which have been the default since VS2005):

const CString strId(id);

Then, you can build the whole file name string:

//
// Build file name using this format:
//
//    file_<generatedIdGoesHere>.csv
//
CString filename(_T("file_"));
filename += strId;
filename += _T(".csv");

As an alternative, you could also use the CString::Format method, e.g.:

CString filename;
filename.Format(_T("file_%s.csv"), strId.GetString());

You can simply pass instances of CString to LPCTSTR parameters in Win32 APIs, since CString offers an implicit conversion to LPCTSTR (i.e. const TCHAR*).

To use CString, you can simply #include <atlstr.h>.

Mr.C64
  • 41,637
  • 14
  • 86
  • 162
  • @JorenHeit std::string can't store UTF-16 strings required by Win32 APIs. You should store UTF-8 strings in std::string, then convert to UTF-16 at the Win32 API boundary. Instead CString works fine with both TCHAR and wchar_t. Using std::string in that context would introduce unnecessary complexity to the OP's code. – Mr.C64 Jun 03 '15 at 10:06
  • @Mr.C64 You shouldn't be using TCHAR at all, anyways. I doubt CString respects unicode completely though. – rubenvb Jun 03 '15 at 10:06
  • 1
    @rubenvb: I also agree on just building in Unicode and using wchar_t, etc. Anyway, the code I posted works fine in Unicode builds, since CString has overloaded constructors that convert from char-string to wchar_t-string. std::string doesn't have something like that. (Moreover CString has convenient methods to load strings from resources, is well integrated in ATL/WTL/MFC, etc. It's great at the Win32 layer of C++ apps.) – Mr.C64 Jun 03 '15 at 10:08
  • @Mr.C64 I must admit I don't have experience with the windows api or UTF-16 strings (never felt the need), but from what I understand, this is what `std::wstring` could be used for. Is this correct or would this still require conversions at the API boundary? – JorenHeit Jun 03 '15 at 10:13
  • 1
    @JorenHeit: std::wstring is made of wchar_ts, which on VC++ are 16-bit units (instead on Linux wchar_ts are 32-bit units). So, yes, on VC++ you can use wstring for UTF-16. However, you _can't_ simply initialize a wstring from a char-string (which is something that comes in handy in OP's code, and CString can do that), and, being platform-agnostic, wstring doesn't have convenient Win32 platform-specific features, like loading strings from resources, etc. I think CString is just fine at the Win32 API layer. Then in other cross plat sections of code, std::string with UTF-8 could be used. – Mr.C64 Jun 03 '15 at 10:19
  • @Mr.C64 Thanks for the explanation. – JorenHeit Jun 03 '15 at 10:23
1

First, convert char to TCHAR (see How to convert char* to TCHAR[ ]? )

Then, concatenate two TCHAR strings using _tcscat().

Community
  • 1
  • 1
No-Bugs Hare
  • 1,557
  • 14
  • 15
1

If you are not using UNICODE character table. Than your TCHAR is equivalent to char.

TCHAR prefix[6] = "file_"; //don't forget to allocate space for null terminator '\0'
TCHAR ext[5] = ".csv"; // size is not 4, remember null terminator
char id[10] = "random"; // no need to use char* here

std::ostringstream oss;
oss << prefix << id << ext << std::endl;
std::cout << oss.str() << std::endl; // gives you file_random.csv as output
yildirim
  • 316
  • 7
  • 14