Mordachai Mordachai - 4 months ago 12
C++ Question

refer to global data from a template function?

Templates generally are inline - you have to supply the definition with the declaration.

Global (static) data requires that there be exactly one definition of the data (but it can be declared multiple times).

So, for a class with static data, one normally declares the static in the class definition (header), and the storage as static in the implementation file (.cpp).

But what does one do for a template that needs to refer to static / global data?

Here's a bit of code to give you something somewhat concrete to consider:

// we represent in a formal manner anything that can be encoded in a MSVS format specification
// A format specification, which consists of optional and required fields, has the following form:
// %[flags][width][.precision][{h | l | ll | w | I | I32 | I64}] type
// based on https://msdn.microsoft.com/en-us/library/56e442dc.aspx
struct FormatSpec
{
enum Size {
normal,
h,
l,
ll,
w,
I,
I32,
I64
};

enum Type {
invalid,
character,
signed_integer,
unsigned_integer,
unsigned_octal,
unsigned_hex,
floating_point,
expontential_floating_point,
engineering_floating_point,
hex_double_floating_point,
pointer,
string,
z_string
};

unsigned fLeftAlign : 1;
unsigned fAlwaysSigned : 1;
unsigned fLeadingZeros : 1;
unsigned fBlankPadding : 1;
unsigned fBasePrefix : 1;

unsigned width;
unsigned precision;

Size size_;
Type type_;
};

struct FormatSpecTypeDatum
{
FormatSpec::Type id; // id
const TCHAR * symbol; // text symbol
};

FormatSpecTypeDatum kTypeSpecs[] =
{
{ FormatSpec::character, _T("c") },
{ FormatSpec::character, _T("C") },
{ FormatSpec::signed_integer, _T("d") },
{ FormatSpec::signed_integer, _T("i") },
{ FormatSpec::unsigned_octal, _T("o") },
{ FormatSpec::unsigned_integer, _T("u") },
{ FormatSpec::unsigned_hex, _T("x") },
{ FormatSpec::unsigned_hex, _T("X") },
{ FormatSpec::expontential_floating_point, _T("e") },
{ FormatSpec::expontential_floating_point, _T("E") },
{ FormatSpec::floating_point, _T("f") },
{ FormatSpec::floating_point, _T("F") },
{ FormatSpec::engineering_floating_point, _T("g") },
{ FormatSpec::engineering_floating_point, _T("G") },
{ FormatSpec::hex_double_floating_point, _T("a") },
{ FormatSpec::hex_double_floating_point, _T("A") },
{ FormatSpec::pointer, _T("p") },
{ FormatSpec::string, _T("s") },
{ FormatSpec::string, _T("S") },
{ FormatSpec::z_string, _T("Z") },
};

template <typename ctype>
bool DecodeFormatSpecType(const ctype * & format, FormatSpec & spec)
{
for (unsigned i = 0; i < countof(kTypeSpecs); ++i)
if (format[0] == kTypeSpecs[i].symbol[0])
{
spec.type_ = kTypeSpecs[i].id;
++format;
return true;
}
return false;
}


It's relatively simple - a symbolic ID to character representation lookup table.

I want to be able to use DecodeFormatSpecType<>() for char, unsigned char, wchar_t, etc.

I could remove the template from DecodeFormatSpecType() and just supply overloaded interfaces for various character types.

The main thing is that the data isn't really changing - an unsigned char 'c' and a wchar_t 'c' and a legacy char 'c' have the exact same value, regardless of the character's storage size (for core ASCII characters this is true, although there are undoubtedly some other encodings such as EDBIC where this isn't true, that's not the problem I'm attempting to solve here).

I just want to understand "how do I construct my C++ libraries so that I can access global data defined in exactly one location - which is stored as an array - and I want the accessing templated code to know the length of the global data, just like I can with normal non-templated code have a global symbol table like what I've shown in my example code by having the table and the implementation that needs its size both exist in the appropriate .cpp file"

Does that make sense?

global data + functions that need to know the exact definition but also can be presented (with an interface) this generic (to a valid domain).

Answer

A function template can use global functions and global data without any problem.

If you want to encapsulate the definition of kTypeSpecs and not have it defined in a header file, you can use couple of functions to provide access to the data.

size_t getNumberOfTypeSpecs();

// Provide read only access to the data.
FormatSpecTypeDatum const* getTypeSpecs();

and then implement DecodeFormatSpecType as

template <typename ctype>
bool DecodeFormatSpecType(const ctype * & format, FormatSpec & spec)
{
   size_t num = getNumberOfTypeSpecs();
   FormatSpecTypeDatum const* typeSpecs = getTypeSpecs();

   for (unsigned i = 0; i < num; ++i)
      if (format[0] == typeSpecs[i].symbol[0])
      {
         spec.type_ = typeSpecs[i].id;
         ++format;
         return true;
      }
   return false;
}

The functions getNumberOfTypeSpecs and getTypeSpecs can be implemented in a .cpp file as:

// Make the data file scoped global variable.
static FormatSpecTypeDatum kTypeSpecs[] =
{
   { FormatSpec::character, _T("c") },
   { FormatSpec::character, _T("C") },
   { FormatSpec::signed_integer, _T("d") },
   { FormatSpec::signed_integer, _T("i") },
   { FormatSpec::unsigned_octal, _T("o") },
   { FormatSpec::unsigned_integer, _T("u") },
   { FormatSpec::unsigned_hex, _T("x") },
   { FormatSpec::unsigned_hex, _T("X") },
   { FormatSpec::expontential_floating_point, _T("e") },
   { FormatSpec::expontential_floating_point, _T("E") },
   { FormatSpec::floating_point, _T("f") },
   { FormatSpec::floating_point, _T("F") },
   { FormatSpec::engineering_floating_point, _T("g") },
   { FormatSpec::engineering_floating_point, _T("G") },
   { FormatSpec::hex_double_floating_point, _T("a") },
   { FormatSpec::hex_double_floating_point, _T("A") },
   { FormatSpec::pointer, _T("p") },
   { FormatSpec::string, _T("s") },
   { FormatSpec::string, _T("S") },
   { FormatSpec::z_string, _T("Z") },
};

size_t getNumberOfTypeSpecs()
{
   return sizeof(kTypeSpecs)/sizeof(kTypeSpecs[0]);
}

FormatSpecTypeDatum const* getTypeSpecs()
{
   return kTypeSpecs;
}

Update, in response to comment by OP

Yes, you can. The following are perfectly valid:

size_t getNumberOfTypeSpecs()
{
   static constexpr size_t num = sizeof(kTypeSpecs)/sizeof(kTypeSpecs[0]);
   return num;
}

constexpr size_t getNumberOfTypeSpecs()
{
   return sizeof(kTypeSpecs)/sizeof(kTypeSpecs[0]);
}