Motivation
A CLI app tries to make use of box drawing characters. But depending on the stdout device this may not be appropriate so there is a -t option to use pure ASCII text.
The concept is just an example, this applies anywhere where we want to select from two or more sets of static constants at runtime.
The question is what is reasonable c++ technique to facilitate this cleanly.
Desired usage Syntax
This is indicative only, but it the constant "invocation" should be terse
int main(int argc, char* argv[]) {
// using derived class style here, but anything would do
auto t = std::strcmp(argv[1], "-t") == 0 ? TxtTerminal() : BdTerminal();
// here we want the syntax to be short
std::cout << "text" << t.horiz << t.right_t << '\n';
}
Already tried
I have tried Base and Derived class style (as above) but there are issues. I can't put one set of constants in TxtTerminal and one in BdTerminal (which extends TxtTerminal).
Using static constants doesn't work, because C++ doesn't support "late static binding" , so it always uses the constants of whichever class you have the instance of.
Using member variables and proper instances of those classes (as hinted at above) doesn't work (nicely) either because the Derived class can't initialise the Base class members directly....
Nor via its constructor initialiser list ...
The derived class would have to pass the entire set to the base class constructor which then modifies the already (BaseClass)initialised values. Really awkward and long winded. I guess passing some struct config { .. }; could work
Similarly using a single class, putting both sets of constants in there in a map<key,config> and using a getter to retrieve the appropriate constant using a constructor initialised bool textmode member state variable.
Maybe with a templated solution taking some template parameter? Well no, because this is a runtime switch.
Similar to the template idea would be some kind of solution with using aliases for 2 separate constant namespaces..same problem..compile time..not runtime.
Using variable templates a bit like the new c++20 maths constants . Same problem, not runtime.
Of course all this const, ie strings of the control character for the "box drawing terminal" are just a bytes long each. Ideally these would just be compacted together as literal initialised const char* in the .text (linux) segment of the executable (std::string seems overkill). Wherever we compose these constants we would want easy concatenation to make the code readable and maintainable. That's not so easy with const char* string literals?
Many options, none seem great. Am I missing something obvious?
EDIT In response to questions below and also to the initial answer by #walnut here is some more fleshed out code using the "static non-instance" approach.
Some unsolved problems therein, see comments:
// NOTE this code compiles and runs, but doesn't quite do what we need
// see comments
#include <cstring>
#include <iostream>
struct TextMode {
static inline const auto cls = "TM cls";
static inline const auto right_t = "TM right_t";
static inline const auto left_t = "TM left_t";
};
struct BlockDrawMode {
static inline const auto cls = "BD cls";
static inline const auto right_t = "BD right_t";
static inline const auto left_t = "BD left_t";
};
struct Terminal {
Terminal(bool textmode = true) { mode = textmode ? &text_mode : &block_drawing_mode; }
// what is this type? some parent class of TextMode/BlockDrawMode?!
// then we get the initilisation loop again...
// ??????? // could be a reference initilised in cstr initilisation, but that's a detail
const TextMode* mode;
static inline const auto text_mode = TextMode{};
static inline const auto block_drawing_mode = TextMode{};
// obviously this needs to be .. ^ BlockDrawMode
// but then doesn't compile because there is no class hierarchy ...
};
int main(int argc, char* argv[]) {
Terminal t{strcmp(argv[1], "-t") == 0};
std::cout << t.mode->cls << '\n' << t.mode->right_t << '\n';
// output (-t makes no difference right now, due to the above issues)
// TM cls
// TM right_t
return 0;
}
EDIT2: I have added a self answer below which uses aggregate initialisation to avoid a lot of the unwanted inheritance complications. Feels a bit "dirty", but looks clean and works fine?
From the comments I gather that what you are looking for is:
If there aren't any other differences in behavior requiring a virtual interface or inheritance, then you can just define a pointer member which is chosen for the correct implementation at construction:
struct Terminal {
Terminal(/* parameters */) {
chooseConfig(/* some arguments */);
}
static constexpr char config1[]{/*...*/};
static constexpr char config2[]{/*...*/};
const char* config;
void chooseConfig(/*arguments*/) {
config = /*condition*/ ? config1 : config2;
};
// use `config` everywhere
};
int main(int argc, char* argv[]) {
Terminal terminal{/* arguments */};
//...
}
If all instances of this class are supposed to share the same configuration and passing the arguments to choose the configuration each time is too cumbersome, then you can also make config and chooseConfig static and instead of calling it in the constructor, call it once in main with Terminal::chooseConfig(/*arguments*/);.
A proposed "self answer":
Here is a version that avoids the hierarchy complications of the config structs. Basically aggregate initialization to the rescue...
This final version shows some proper terminal control strings and some tweaks using a ref to static inline struct. plus it uses designated initialisers. Note selective overrides from the Base Struct Config details would be possible if desired. All the "keys" are automatically checked at compile time, so prevent mistakes during maintenance.
struct Mode {
const char* const esc = "";
const char* const cls = "";
const char* const bd_on = "";
const char* const bd_off = "";
const char* const underline_on = "";
const char* const black = "";
const char* const red = "";
const char* const green = "";
const char* const yellow = "";
const char* const blue = "";
const char* const magenta = "";
const char* const cyan = "";
const char* const white = "";
const char* const reset = "";
const char horiz = '-';
const char vert = '|';
const char right_t = '|';
const char left_t = '|';
const char bottom_t = '|';
const char top_t = '|';
const char intersec = '|';
};
struct Terminal {
static inline const Mode text_mode;
// warning: this is a C99 extension until C++20 comes in
// but it nicely compile checks and self documents the code
// supported by gcc4.7, clang3.0 and msvc19.21
static inline const Mode box_draw_mode{
.esc = "\x1b",
.cls = "\x1b[2J",
.bd_on = "\x1b(0",
.bd_off = "\x1b(B",
.underline_on = "\x1b[4m",
.black = "\x1b[30m",
.red = "\x1b[31m",
.green = "\x1b[32m",
.yellow = "\x1b[33m",
.blue = "\x1b[34m",
.magenta = "\x1b[35m",
.cyan = "\x1b[36m",
.white = "\x1b[37m",
.reset = "\x1b[0m",
.horiz = '\x71',
.vert = '\x78',
.right_t = '\x75',
.left_t = '\x74',
.bottom_t = '\x76',
.top_t = '\x77',
.intersec = '\x6e',
};
const Mode& m;
Terminal(bool textmode = true) : m{textmode ? text_mode : box_draw_mode} {}
};
int main(int argc, char* argv[]) {
Terminal t1{true};
Terminal t2{false};
// usage
// std::cout << t1.m.cls << '\n' << t1.m.right_t << '\n';
// std::cout << t2.m.cls << '\n' << t2.m.right_t << '\n';
return 0;
}
Here is a godbolt (note that clang>3 and gcc>4.7 compile the designated initialisers just fine already). Also note that the godbolt shows that on -O3 the initializers get optimized away to the point were the char values just become inlined register loads. On O1 we can clearly see the strings laid out in .text segment and literally the only thing that happen during Terminal construction is the setting of a single pointer for Mode& m to point to one of the 2 structures: mov qword ptr [rdi], rcx. Good?
Related
I have some code that I use to give me a list of strings than match the names of enum members so that I can parse text files that use the names easily into the enums. (I actually generate it automatically with a macro but that is a different story.) The code works fine on modern compilers running C++17 but one of my target platforms does not have C++17 support and I'd like to come up with an alternative that works on C++14 (and ideally C++11). The current code is:
class A
{
enum MatchType { Linear, Square };
static constexpr const char * matchTypeStrings[ 2 ] = { "Linear", "Square" };
static const size_t matchTypeCount = 2;
};
I'd rather not give the enum global scope because that never ends well, and ideally I don't want to add anything to one of the .cpp files (which of course will fix the linker error, but I think it isn't recommended for C++17 code, and won't work with my macro anyway). I was whether I could create a member function that could return the string value instead but I can't think of a neat way of doing it.
Using a static member function:
#include <cstdint>
#include <iostream>
class A
{
public:
enum MatchType { Linear, Square };
static const char * matchTypeStrings(MatchType type)
{
static const char * const matchTypeStrings[ 2 ] = { "Linear", "Square" };
return matchTypeStrings[type];
}
static const std::size_t matchTypeCount = 2;
};
int main()
{
std::cout << A::matchTypeStrings(A::Linear) << '\n';
std::cout << A::matchTypeStrings(A::Square) << '\n';
}
It is important to note that this obviously is not constexpr, however GCC and Clang manage to generate similar code with optimizations enabled. ( On Compiler Explorer: Your version, Static function )
Everything works in Visual Studio 2017, but I get linker errors in GCC (6.5.0).
Here is some sample code that isolates my problem:
#include <iostream>
struct Foo{
static constexpr const char* s[] = {"one","two","three"};
};
int main(){
std::cout << Foo::s[0] << std::endl; //this works in both compilers
const char* const* str_ptr = nullptr;
str_ptr = Foo::s; //LINKER ERROR in GCC; works in VS
std::cout << str_ptr[1] << std::endl; //works in VS
return 0;
}
In GCC, I get undefined reference to 'Foo::s'. I need the initialization of Foo::s to stay in the declaration of the struct, which is why I used constexpr. Is there a way to reference Foo::s dynamically, i.e. with a pointer?
More Background Info
I'll now explain why I want to do this. I am developing embedded software to control configuration of a device. The software loads config files that contain the name of a parameter and its value. The set of parameters being configured is determined at compile-time, but needs to be modular so that it's easy to add new parameters and expand them as development of the device continues. In other words, their definition needs to be in one single place in the code base.
My actual code base is thousands of lines and works in Visual Studio, but here is a boiled-down toy example:
#include <iostream>
#include <string>
#include <vector>
//A struct for an arbitrary parameter
struct Parameter {
std::string paramName;
int max_value;
int value;
const char* const* str_ptr = nullptr;
};
//Structure of parameters - MUST BE DEFINED IN ONE PLACE
struct Param_FavoriteIceCream {
static constexpr const char* n = "FavoriteIceCream";
enum { vanilla, chocolate, strawberry, NUM_MAX };
static constexpr const char* s[] = { "vanilla","chocolate","strawberry" };
};
struct Param_FavoriteFruit {
static constexpr const char* n = "FavoriteFruit";
enum { apple, banana, grape, mango, peach, NUM_MAX };
static constexpr const char* s[] = { "apple","banana","grape","mango","peach" };
};
int main() {
//Set of parameters - determined at compile-time
std::vector<Parameter> params;
params.resize(2);
//Configure these parameters objects - determined at compile-time
params[0].paramName = Param_FavoriteIceCream::n;
params[0].max_value = Param_FavoriteIceCream::NUM_MAX;
params[0].str_ptr = Param_FavoriteIceCream::s; //!!!! LINKER ERROR IN GCC !!!!!!
params[1].paramName = Param_FavoriteFruit::n;
params[1].max_value = Param_FavoriteFruit::NUM_MAX;
params[1].str_ptr = Param_FavoriteFruit::s; //!!!! LINKER ERROR IN GCC !!!!!!
//Set values by parsing files - determined at run-time
std::string param_string = "FavoriteFruit"; //this would be loaded from a file
std::string param_value = "grape"; //this would be loaded from a file
for (size_t i = 0; i < params.size(); i++) {
for (size_t j = 0; j < params[i].max_value; j++) {
if (params[i].paramName == param_string
&& params[i].str_ptr[j] == param_value) {
params[i].value = j;
break;
}
}
}
return 0;
}
As you can see, there are enums and string arrays involved and these need to match, so for maintenance purposes I need to keep these in the same place. Furthermore, since this code is already written and will be used in both Windows and Linux environments, the smaller the fix, the better. I would prefer not to have to re-write thousands of lines just to get it to compile in Linux. Thanks!
The program is valid in C++17. The program is not valid in C++14 or older standards. The default standard mode of GCC 6.5.0 is C++14.
To make the program conform to C++14, you must define the static member (in exactly one translation unit). Since C++17, the constexpr declaration is implicitly an inline variable definition and thus no separate definition is required.
Solution 1: Upgrade your compiler and use C++17 standard (or later if you're from the future), which has inline variables. Inline variables have been implemented since GCC 7.
Solution 2: Define the variable outside the class definition in exactly one translation unit (the initialisation remains in the declaration).
It looks like you are not using C++17, and pre-C++17 this is undefined behavior, and one that I really dislike. You do not have a definition for your s, but you are ODR-using it, which means that you need to have a definition.
To define this, you have to define it in .cpp file.
In C++17 that would be the valid code. I am not familiar with MSVC, so I am not sure why it works fine there - be it that it is compiled as C++17, or because it is just a different manifestation of undefined behavior.
For C++98, C++11, and C++14 you need to explicitly tell the compiler where the initialization of Foo::s lives (see below) and you are good to go.
struct Foo{
static const char* s[];
};
const char* Foo::s[] = {"one","two","three"};
And as explained in one of the comments your initialization is OK from C++17.
This is my first question:
I am trying to read "global attributes" from a NetCDF file (using the C++ legacy API). By "global attribute" I mean an attribute that was added to a NcFile, not to a NcVar.
For most things the "Example netCDF programs" are useful -- but there is no example for "global attributes".
Consulting the "netcdfcpp.h" I find a few things:
NcFile has a member function: NcAtt* get_att(NcToken) const;
NcAtt has NO public constructor
NcAtt is friends with NcFile: friend class NcFile;
NcAtt has a private constructor: NcAtt( NcFile*, NcToken);
NcAtt has a public member function NcValues* values( void ) const;
NcValues has an API defined through the ncvalues.h header
My coding skills are insufficient to understand how I get back at the string/int/float stored as NcValue, in an NcAtt class within NcFile.
Attached is a example code of my problem "NetCDF_test.cpp", with the critical part missing from the implementation of the "LoadNetCDF" function.
The code compiles OK with: (edit: also, "TestFile.nc" is created correctly)
g++ -c NetCDF_test.cpp -o NetCDF_test.o
g++ -o NCTEST NetCDF_test.o -lnetcdf_c++ -lnetcdf
Example code:
#include <iostream> // provides screen output (i.e. std::cout<<)
#include <netcdfcpp.h>
struct MyStructure {
std::string MyString;
int MyInt;
float MyFloat;
MyStructure(); // default constructor
int SaveNetCDF(std::string); // Save the struct content to "global attributes" in NetCDF
int LoadNetCDF(std::string); // Load the struct content from "global attributes" in NetCDF
};
MyStructure::MyStructure(void)
{
MyString = "TestString";
MyInt = 123;
MyFloat = 1.23;
}
int MyStructure::SaveNetCDF(std::string OUTPUT_FILENAME)
{
NcError err(NcError::silent_nonfatal);
static const int NC_ERR = 2;
NcFile NetCDF_File(OUTPUT_FILENAME.c_str(), NcFile::Replace);
if(!NetCDF_File.is_valid()) {return NC_ERR;}
if(!(NetCDF_File.add_att("MyString",MyString.c_str()))) {return NC_ERR;}
if(!(NetCDF_File.add_att("MyInt",MyInt))) {return NC_ERR;}
if(!(NetCDF_File.add_att("MyFloat",MyFloat))) {return NC_ERR;}
return 0;
}
int MyStructure::LoadNetCDF(std::string INPUT_FILENAME)
{
NcError err(NcError::silent_nonfatal);
static const int NC_ERR = 2;
NcFile NetCDF_File(INPUT_FILENAME.c_str(), NcFile::ReadOnly);
if(!NetCDF_File.is_valid()) {return NC_ERR;}
// ???? This is where I am stuck.
// How do I read the global attribute from the NetCDF_File ??
return 0;
}
int main()
{
std::cout<< "START OF TEST.\n";
MyStructure StructureInstance; // datamembers initialized by constructor
StructureInstance.SaveNetCDF("TestFile.nc");
StructureInstance.MyString = "Change string for sake of testing";
StructureInstance.MyInt = -987;
StructureInstance.MyFloat = -9.87;
StructureInstance.LoadNetCDF("TestFile.nc"); // data members are supposed to be read from file
std::cout<< "Now the data members of StructureInstance should be TestString, 123, and 1.23\n";
std::cout<< StructureInstance.MyString << " ; " << StructureInstance.MyInt << " ; " << StructureInstance.MyFloat <<"\n";
std::cout<< "END OF TEST.\n";
}
It's pretty clearly spelled out in the C++ users guide: http://www.unidata.ucar.edu/software/netcdf/docs/netcdf-cxx/Class-NcAtt.html#Class-NcAtt
"Because attributes are only associated with open netCDF files, there are no public constructors for this class. Use member functions of NcFile and NcVar to get netCDF attributes or add new attributes."
global attributes are attributes on the file (as opposed to variable attributes which are, well, attributes on the variable)
NetCDF_File.num_atts() returns how many global attributes. The get_att() methods (overloaded in various ways) will get you an attribute.
consult http://www.unidata.ucar.edu/software/netcdf/docs/netcdf-cxx/Class-NcFile.html#Class-NcFile
Many thanks to Rob Latham for the links to a commented description of the NetCDF API (legacy C++). With the information given there I was able to figure it out:
NcAtt inherents form NcTypedComponent a set of member functions for accessing data stored within a given NcAtt: (int n == n-th element)
ncbyte as_ncbyte( int n ) const
char as_char( int n ) const
short as_short( int n ) const
int as_int( int n ) const
nclong as_nclong( int n ) const // deprecated
long as_long( int n ) const
float as_float( int n ) const
double as_double( int n ) const
char* as_string( int n ) const
But still, the constructor for NcAtt is private and the only access point to an existing NcAtt is through the NcFile member function NcVar* get_var(NcToken name) const -- which only returns a pointer. Hence the straight forward usage does not work:
int MyInt = MyNcFile.get_att("MyInt").as_int(0); // DOES NOT COMPILE
However, dereferencing the pointer returned by get_att does the trick.
int MyInt = (*MyNcFile.get_att("MyInt")).as_int(0); // WORKS
For sake of completeness I include below the implementation of MyStructure::LoadNetCDF for the example code of my original question.
int MyStructure::LoadNetCDF(std::string INPUT_FILENAME)
{
NcError err(NcError::silent_nonfatal);
static const int NC_ERR = 2;
NcFile NetCDF_File(INPUT_FILENAME.c_str(), NcFile::ReadOnly);
if(!NetCDF_File.is_valid()) {return NC_ERR;}
// NcAtt constructor is private, but one can obtain the pointer to an existing NcAtt
NcAtt* PointerToMyIntNcAtt = NetCDF_File.get_att("MyInt");
// Now, using the dereferencing operator one has access to the member functions that NcAtt inherents from NcTypedComponent
if(!(*PointerToMyIntNcAtt).is_valid()) {return NC_ERR;}
std::cout<< "Is MyInt a valid NcAtt? "<< (*PointerToMyIntNcAtt).is_valid()<<"\n";
// The concise way of writing the access to NetCDF "global attributes"" of type int/float/string
MyInt = (*NetCDF_File.get_att("MyInt")).as_int(0);
MyFloat = (*NetCDF_File.get_att("MyFloat")).as_float(0);
MyString = (*NetCDF_File.get_att("MyString")).as_string(0);
return 0;
}
The program fails while compiling the code. Compiler points to printf("Version = '%s'\n", gABXVER). I guess that I actually can't write gABXVER = "V1R1", but I don't have any other idea.
class CISPFVar_BINSTR : public CISPFVar
{
protected:
char* m_pBuffer;
long m_bDefined;
public:
...
void Initialize(char* szName, long lSize, int bDefineVar = 1)
{
Uninitialize();
ZStrToCharArray(szName, m_cName, 8);
m_Size = lSize+1;
m_pBuffer = (char*)malloc(m_Size);
m_pBuffer[0] = 0;
if (bDefineVar)
ISPLINK(__VDEFINE, m_cName, m_pBuffer, __BINSTR, &m_Size);
m_bDefined = bDefineVar;
}
...
};
CISPFVar_BINSTR gABXVER;
char szLoadLibraryPath[50];
int main(
int argc,
char* argv[])
{
if (argc > 1)
if (argv[1]) strcpy(szLoadLibraryPath, argv[1]);
gABXVER.Initialize("ABXVER",4);
gABXVER = "V1R1";
printf("Version = '%s'\n", gABXVER);
return 0;
};
When you use %s in printf family of functions, the corresponding argument type needs to be const char* or something that can be converted to const char*. The argument you are using is not such a type. Perhaps you meant to use:
printf("Version = '%s'\n", gABXVER.m_pBuffer);
The compiler should compile just fine (with possible warnings for printf) because printf doesn't care what you pass to it (beyond the first parameter) or whether it matches the format string. Modern compilers or error checking progs like lint will issue a warning if the params obviously don't match, and if you have a setting "treat warnings as errors", the prog may fail to compile.
That said, CISPFVar_BINSTR needs a public copy constructor if you want to pass it as a parameter by value to a function (because at least semantically a copy will be made). Does it have one? As others remarked it's customary to help your helpers by providing any information you have. Here we are badly missing the compiler errors. (You can edit your post at any time.)
I could imagine that the class has a conversion to char* or std::string, so it may suffice to try either printf("Version = '%s'\n", (char *)gABXVER) or printf("Version = '%s'\n", (std::string(gABXVER)).c_str() ).
You can only printf things that have format specifiers designed specifically for them. There is no format specifier that accepts a value of class type, so you cannot printf one directly.
The best thing you can do is explicitly convert your object to a const char* and pass the result to printf.
In c++ you can use many techniques to implement things like streaming operators
#include <iostream>
class Whatever
{
int value = 42;
public:
int Get() const {
return value;
}
friend std::ostream& operator<<(std::ostream&, Whatever const&);
};
std::ostream& operator<<(std::ostream& os, Whatever const& what) {
os << what.Get();
return os;
}
int main() {
Whatever x;
std::cout << x << std::endl;
}
printf is unsafe
In effect, you're doing serialization of your object into a readable string.
In C++ I wanted to define a constant that I can use in another function, A short answer on how to do this will be fine..
Lets say at the beginning of my code I want to define this constant:
//After #includes
bool OS = 1; //1 = linux
if (OS) {
const ??? = "clear";
} else {
const ??? = "cls";
}
I don't know what type to use to define the "clear" string... I'm so confused.
Later on I want to use it within a function:
int foo() {
system(::cls); //:: for global
return 0;
}
How would I define the string up top, and use the string down below? I heard char only had one character and things... I'm not sure how to use , since it says it's converting string into const char or something.
char* isn't quite a char. char* is basically a string (it's what strings were before C++ came along).
For illustration:
int array[N]; // An array of N ints.
char str[N]; // An array of N chars, which is also (loosely) called a string.
char[] degrades to char*, so you'll often see functions take a char*.
To convert std::string to const char*, you can simply call:
std::string s;
s.c_str()
In this case, it's common to use the preprocessor to define your OS. This way you can use the compiler to do the platform specific stuff:
#ifdef OS_LINUX
const char cls[] = "clear";
#elif OS_WIN
const char cls[] = "cls";
#endif
One thing you may want to consider is making it a function. This avoids nasty dependencies of global construction order.
string GetClearCommand() {
if (OS == "LINUX") {
return "clear";
} else if (OS == "WIN") {
return "cls";
}
FAIL("No OS specified?");
return "";
}
What it looks like you're trying to do is this:
#include <iostream>
using namespace std;
#ifdef LINUX
const char cls[] = "LINUX_CLEAR";
#elif WIN
const char cls[] = "WIN_CLEAR";
#else
const char cls[] = "OTHER_CLEAR";
#endif
void fake_system(const char* arg) {
std::cout << "fake_system: " << arg << std::endl;
}
int main(int argc, char** argv) {
fake_system(cls);
return 0;
}
// Then build the program passing your OS parameter.
$ g++ -DLINUX clear.cc -o clear
$ ./clear
fake_system: LINUX_CLEAR
Here's the problem, you're suffering from going out of scope with the variables. If I declare something within brackets, it only exists within the brackets.
if( foo ){
const char* blah = "blah";
}
Once we leave the if statement, the variable blah disappears. You'll need to instantiate it non-locally to whatever brackets you write. Hence:
void Bar(){
const char* blah = "blah";
if( foo ){
//blah exists within here
}
}
However, blah will not exist outside of Bar. Get it?
Yet another option is to create a class with a bunch of static methods. Create a new method for each command. Something like:
// in sys-commands.h
class SystemCommands {
public:
static char const* clear();
static char const* remove();
};
This gives you a few nice options for the implementation. The nicest one is to have a separate implementation file for each platform that you select during compile time.
// in sys-commands-win32.cpp
#include "sys-commands.h"
char const* SystemCommands::clear() { return "cls"; }
char const* SystemCommands::remove() { return "erase /f/q"; }
// in sys-commands-macosx.cpp
#include "sys-commands.h"
char const* SystemCommands::clear() { return "/usr/bin/clear"; }
char const* SystemCommands::remove() { return "/bin/rm -fr"; }
Which file gets compiled will determine which command set will be used. Your application code will look like:
#include <cstdlib>
#include "sys-commands.h"
int main() {
std::system(SystemCommands::clear());
return 0;
}
Edit: I forgot to mention that I prefer static functions to global constants for a bunch of reasons. If nothing else, you can make them non-constant without changing their types - in other words, if you ever have to select the command set based on runtime settings, the user code does not have to change or even be aware that such a change occurred.
You can use a common header file and link to different modules depending on the systen:
// systemconstants.hpp
#ifndef SYSTEM_CONSTANTS_HPP_INCLUDED
#define SYSTEM_CONSTANTS_HPP_INCLUDED
namespace constants {
extern const char cls[]; // declaration of cls with incomplete type
}
#endif
In case of Linux, just compile and link to this one:
// linux/systemconstants.cpp
#include "systemconstants.hpp"
namespace constants {
extern const char cls[] = "clear";
}
In case of Windows, just compile and link to this one:
// windows/systemconstants.cpp
#include "systemconstants.hpp"
namespace constants {
extern const char cls[] = "cls";
}
System-specific translation units could be placed in specific subdirectories (linux/, windows/, etc) of which one could be automatically selected during the build process. This extends to many other things, not just string constants.