# Improving poor man's translations mechanism in c++98 program

In order to add basic translation capabilities in an old c++98 program I've come up to a basic and shameless code summarized by this snippet:

#include <string>
#include <iostream>

// Translation ids
enum
{
TR_SAVE,
TR_MSGWARNRESET,
TR_MSGAPPLYCHANGES
};

// (Returning an array reference)
const std::string (&resolve_translation(const std::string& lang))[]
{
static const std::string tr_en[] =
{
"Save",       // TR_SAVE
"Current values will be lost, are you sure?", // TR_MSGWARNRESET
"Apply changes to material %s?" // TR_MSGAPPLYCHANGES
};

static const std::string tr_it[] =
{
"Salva",    // TR_SAVE
"I valori correnti saranno persi, sei sicuro?", // TR_MSGWARNRESET
"Applicare modifiche al materiale %s?" // TR_MSGAPPLYCHANGES
};

static const std::string tr_es[] =
{
"Salvar",   // TR_SAVE
"Los valores actuales se perderán, está seguro?", // TR_MSGWARNRESET
"¿Aplicar cambios al material %s?" // TR_MSGAPPLYCHANGES
};

static const std::string tr_fr[] =
{
"Enregistrer", // TR_SAVE
"Les valeurs actuelles seront perdues, êtes-vous sûr?", // TR_MSGWARNRESET
"Appliquer les modifications au matériau %s?" // TR_MSGAPPLYCHANGES
};

if(lang=="en") return tr_en;
if(lang=="it") return tr_it;
if(lang=="fr") return tr_fr;
if(lang=="es") return tr_es;
return tr_en; // default
}

int main()
{
std::string lang = "it"; // unknown at compile time
const std::string (&tr)[] = resolve_translation(lang);
std::cout << tr[TR_MSGWARNRESET] << '\n';
}


The usage is cumbersone because needs a local call to resolve_translation, however I can compile it in bcc and g++ and is working, but I'm not sure why does not compile with clang and msvc, I fear that there's some major problem under the rug.

I'm seeking some advices to improve it.

• I'm sure Clang and MSVC give some kind of error message and location, right? You might include that detail so someone might be able to tell you want it means. Aug 26 '21 at 15:16
• @JDługosz Yes! I didn't want to stuff too many things, but opted to include the compiler explorer link exactly for that reason
– MatG
Aug 26 '21 at 15:50
• I didn't follow the link; assumed it was a Stack Exchange snippet that was equivalent to the listing with maybe some boilerplate that's not shown below. Aug 27 '21 at 19:40
• GCC shows the same error when invoked in a standards-conformant mode: g++ -std=c++98 -Wpedantic -Wall -Wextra - "warning: conversions to arrays of unknown bound are only available with -std=c++2a or -std=gnu++2a". Aug 28 '21 at 7:48
• You must not edit the code in a Code Review after it has been posted and answered. If you have a new revision, you can post it in another Question, and refer back to this one, and you can edit this one to note that there is a newer one too. Aug 31 '21 at 15:15

Improvements:

The return type is strange and awkward enough that it needs a comment to explain it! What benefit does returning a reference to an array have over simply returning a pointer to the first element?

It would be better to return an object. It might simply contain a pointer and length, but it means you could update it to support dynamically loaded tables or other new features, and do error checking on the operator[], and use a strong type for the subscript as well (that is, it requires the enumeration constant, not just any old integer).

Building an array of std::string is inefficient since it copies all of the literals into the string object at run time. If you're compiling as C++98 you don't have string_view built in, but you could supply your own as part of the program, or make it an array of plain char* instead. I guess it depends on how the return values are being used: if it repeatedly needs to convert that to a string you'd rather have it done and remembered. But you don't need to copy and consume memory for all the unused tables. That's another reason to make it an abstract object, as it can be optimized and improved "under the hood" later without changing the usage.

# the compiler error

reference to incomplete type 'const std::string []' could not bind to an lvalue of type 'const std::string [4]'

The function's type is declared without bounds. It's not like an initializer where the actual array in the return statement will inform it; though apparently g++ accepts that as an extension (sort of an implicit partial auto). From cppreference: (emphasis mine)

If expr is omitted in the declaration of an array, the type declared is "array of unknown bound of T", which is a kind of incomplete type, except when used in a declaration with an aggregate initializer.

References and pointers to arrays of unknown bound can be formed, but cannot be initialized or assigned from arrays and pointers to arrays of known bound.

Your code is actually illegal in standard C++.

• All your points make sense, I'll consider each one to straighten up this mess.
– MatG
Aug 27 '21 at 6:02
• The return type "needs a comment" - or perhaps a name? Giving it a name leads naturally towards the next paragraph, a user-defined object type. Aug 28 '21 at 7:44
• @TobySpeight In other words, a typedef would improve the readability, that's a valuable observation. Specifying also the array size would make the code more conformant. I'll try to smooth the rough edges keeping the runtime overhead as minimum ad possible.
– MatG
Aug 28 '21 at 9:09
• @MatG, does C++98 not have using? If not, then typedef would have to do. Aug 28 '21 at 10:05