I realize this has been asked before more than once on SO but I couldn't find a question explicitly looking for a current solution to this issue with C++11, so here we go again..
Can we conveniently get the string value of an enum with C++11?
I.e. is there (now) any built-in functionality in C++11 that allows us to get a string representation of enum types as in
typedef enum {Linux, Apple, Windows} OS_type;
OS_type myOS = Linux;
cout << myOS
that would print Linux on the console?
The longstanding and unnecessary lack of a generic enum-to-string feature in C++ (and C) is a painful one. C++11 didn't address this, and as far as I know neither will C++14.
Personally I'd solve this problem using code generation. The C preprocessor is one way--you can see some other answers linked in the comments here for that. But really I prefer to just write my own code generation specifically for enums. It can then easily generate to_string (char*), from_string, ostream operator<<, istream operator<<, is_valid, and more methods as needed. This approach can be very flexible and powerful, yet it enforces absolute consistency across many enums in a project, and it incurs no runtime cost.
Do it using Python's excellent "mako" package, or in Lua if you're into lightweight, or the CPP if you're against dependencies, or CMake's own facilities for generating code. Lots of ways, but it all comes down to the same thing: you need to generate the code yourself--C++ won't do this for you (unfortunately).
In my opinion, the most maintainable approach is to write a helper function:
const char* get_name(OS_type os) {
switch (os) {
case Linux: return "Linux";
case Apple: return "Apple";
case Windows: return "Windows";
}
}
It is a good idea not to implement the "default" case, since doing so will ensure that you get a compiler warning if you forget to implement a case (with the right compiler and compiler settings).
I like a hack using the C preprocessor, which I first saw here:
http://blogs.msdn.com/b/vcblog/archive/2008/04/30/enums-macros-unicode-and-token-pasting.aspx .
It uses the token-pasting operator # .
// This code defines the enumerated values:
#define MY_ENUM(x) x,
enum Fruit_Type {
MY_ENUM(Banana)
MY_ENUM(Apple)
MY_ENUM(Orange)
};
#undef MY_ENUM
// and this code defines an array of string literals for them:
#define MY_ENUM(x) #x,
const char* const fruit_name[] = {
MY_ENUM(Banana)
MY_ENUM(Apple)
MY_ENUM(Orange)
};
#undef MY_ENUM
// Finally, here is some client code:
std::cout << fruit_name[Banana] << " is enum #" << Banana << "\n";
// In practice, those three "MY_ENUM" macro calls will be inside an #include file.
Frankly, it's ugly and. but you end up typing your enums exactly ONCE in an include file, which is more maintainable.
BTW, on that MSDN blog link (see above) a user made a comment with a trick that makes the whole thing much prettier, and avoids #includes:
#define Fruits(FOO) \
FOO(Apple) \
FOO(Banana) \
FOO(Orange)
#define DO_DESCRIPTION(e) #e,
#define DO_ENUM(e) e,
char* FruitDescription[] = {
Fruits(DO_DESCRIPTION)
};
enum Fruit_Type {
Fruits(DO_ENUM)
};
// Client code:
std::cout << FruitDescription[Banana] << " is enum #" << Banana << "\n";
(I just noticed that 0x17de's answer also uses the token-pasting operator)
Here is a simple example using namespaces and structs.
A class is created for each enum item. In this example i chose int as the type for the id.
#include <iostream>
using namespace std;
#define ENUMITEM(Id, Name) \
struct Name {\
static constexpr const int id = Id;\
static constexpr const char* name = #Name;\
};
namespace Food {
ENUMITEM(1, Banana)
ENUMITEM(2, Apple)
ENUMITEM(3, Orange)
}
int main() {
cout << Food::Orange::id << ":" << Food::Orange::name << endl;
return 0;
}
Output:
3:Orange
== Update ==
Using:
#define STARTENUM() constexpr const int enumStart = __LINE__;
#define ENUMITEM(Name) \
struct Name {\
static constexpr const int id = __LINE__ - enumStart - 1;\
static constexpr const char* name = #Name;\
};
and using it once before the first usage of ENUMITEM the ids would not be needed anymore.
namespace Food {
STARTENUM()
ENUMITEM(Banana)
ENUMITEM(Apple)
ENUMITEM(Orange)
}
The variable enumStart is only accessible through the namespace - so still multiple enums can be used.
You can use macro to solve this problem:
#define MAKE_ENUM(name, ...) enum class name { __VA_ARGS__}; \
static std::vector<std::string> Enum_##name##_init(){\
const std::string content = #__VA_ARGS__; \
std::vector<std::string> str;\
size_t len = content.length();\
std::ostringstream temp;\
for(size_t i = 0; i < len; i ++) {\
if(isspace(content[i])) continue;\
else if(content[i] == ',') {\
str.push_back(temp.str());\
temp.str(std::string());}\
else temp<< content[i];}\
str.push_back(temp.str());\
return str;}\
static const std::vector<std::string> Enum_##name##_str_vec = Enum_##name##_init();\
static std::string to_string(name val){\
return Enum_##name##_str_vec[static_cast<size_t>(val)];\
}\
static std::string print_all_##name##_enum(){\
int count = 0;\
std::string ans;\
for(auto& item:Enum_##name##_str_vec)\
ans += std::to_string(count++) + ':' + item + '\n';\
return ans;\
}
As the static variable can only be initialized once, so the Enum_##name##_str_vec will use the Enum_##name##_init() function to initialize itself at first.
The sample code is as below:
MAKE_ENUM(Analysis_Time_Type,
UNKNOWN,
REAL_TIME,
CLOSSING_TIME
);
Then you can use below sentence to print an enum value:
to_string(Analysis_Time_Type::UNKNOWN)
And use below sentence to print all enum as string:
print_all_Analysis_Time_Type_enum()
As mentioned, there is no standard way to do this. But with a little preprocessor magic (similar to AlejoHausner's second contribution) and some template magic, it can be fairly elegant.
Include this code once:
#include <string>
#include <algorithm>
#define ENUM_VALS( name ) name,
#define ENUM_STRINGS( name ) # name,
/** Template function to return the enum value for a given string
* Note: assumes enums are all upper or all lowercase,
* that they are contiguous/default-ordered,
* and that the first value is the default
* #tparam ENUM type of the enum to retrieve
* #tparam ENUMSIZE number of elements in the enum (implicit; need not be passed in)
* #param valStr string version of enum value to convert; may be any capitalization (capitalization may be modified)
* #param enumStrs array of strings corresponding to enum values, assumed to all be in lower/upper case depending upon
* enumsUpper
* #param enumsUpper true if the enum values are in all uppercase, false if in all lowercase (mixed case not supported)
* #return enum value corresponding to valStr, or the first enum value if not found
*/
template <typename ENUM, size_t ENUMSIZE>
static inline ENUM fromString(std::string &valStr, const char *(&enumStrs)[ENUMSIZE], bool enumsUpper = true) {
ENUM e = static_cast< ENUM >(0); // by default, first value
// convert valStr to lower/upper-case
std::transform(valStr.begin(), valStr.end(), valStr.begin(), enumsUpper ? ::toupper : ::tolower);
for (size_t i = 0; i< ENUMSIZE; i++) {
if (valStr == std::string(enumStrs[i])) {
e = static_cast< ENUM >(i);
break;
}
}
return e;
}
Then define each enum like so:
//! Define ColorType enum with array for converting to/from strings
#define ColorTypes(ENUM) \
ENUM(BLACK) \
ENUM(RED) \
ENUM(GREEN) \
ENUM(BLUE)
enum ColorType {
ColorTypes(ENUM_VALS)
};
static const char* colorTypeNames[] = {
ColorTypes(ENUM_STRINGS)
};
You only have to enumerate the enum values once and the code to define it is fairly compact and intuitive.
Values will necessarily be numbered in the default way (ie, 0,1,2,...). The code of fromString() assumes that enum values are in either all uppercase or all lowercase (for converting from strings) that the default value is first, but you can of course change how these things are handled.
Here is how you get the string value:
ColorType c = ColorType::BLUE;
std::cout << colorTypeNames[c]; // BLUE
Here is how you set the enum from a string value:
ColorType c2 = fromString<ColorType>("Green", colorTypeNames); // == ColorType::GREEN
Related
After understanding that GCC supports Compound Literals, where an anonymous structure can be filled using a {...} initaliser.
Then consider that gcc accepts (with limitations) variable length structures if the last element is variable length item.
I would like to be able to use macros to fill out lots of tables where most of the data stays the same from compile time and only a few fields change.
My structure are complicated, so here is a simpler working example to start with as a demonstration of the how it is to be used.
#include <stdio.h>
typedef unsigned short int uint16_t;
typedef unsigned long size_t;
#define CONSTANT -20
// The data we are storing, we don't need to fill all fields every time
typedef struct dt {
uint16_t a;
const int b;
} data_t;
// An incomplete structure definiton that matches the general shape
typedef struct ct {
size_t size;
data_t data;
char name[];
} complex_t;
// A typedef to make the code look cleaner
typedef complex_t * complex_t_ptr;
// A macro to generate instances of objects
#define CREATE(X, Y) (complex_t_ptr)&((struct { \
size_t size; \
data_t data; \
char name[sizeof(X)]; \
} ) { \
.size = sizeof(X), \
.data = { .a = Y, .b = CONSTANT }, \
.name = X \
})
// Create an array number of structure instance and put pointers those objects into an array
// Note each object may be a different size.
complex_t_ptr data_table[] = {
CREATE("DATA1", 1),
CREATE("DATA2_LONGER", 2),
CREATE("D3S", 3),
};
static size_t DATA_TABLE_LEN = sizeof(data_table) / sizeof(typeof(0[data_table]));
int main(int argc, char **argv)
{
for(uint16_t idx=0; idx<DATA_TABLE_LEN; idx++)
{
complex_t_ptr p = data_table[idx];
printf("%15s = (%3u, %3d) and is %3lu long\n", p->name, p->data.a, p->data.b, p->size);
}
return 0;
}
$ gcc test_macro.c -o test_macro
$ ./test_macro
DATA1 = ( 1, -20) and is 6 long
DATA2_LONGER = ( 2, -20) and is 13 long
D3S = ( 3, -20) and is 4 long
So far so good...
Now, what if we want to create a more complicated object?
//... skipping the rest as hopefully you have the idea by now
// A more complicated data structure
typedef struct dt2 {
struct {
unsigned char class[10];
unsigned long start_address;
} xtra;
uint16_t a;
const int b;
} data2_t;
// A macro to generate instances of objects
#define CREATE2(X, Y, XTRA) (complex2_t_ptr)&((struct { \
size_t size; \
data2_t data; \
char name[sizeof(X)]; \
} ) { \
.size = sizeof(X), \
.data = { .xtra = XTRA, .a = Y, .b = CONSTANT }, \
.name = X \
})
// Again create the table
complex2_t_ptr bigger_data_table[] = {
CREATE2("DATA1", 1, {"IO_TBL", 0x123456L}),
CREATE2("DATA2_LONGER", 2, {"BASE_TBL", 0xABC123L}),
CREATE2("D3S", 3, {"MAIN_TBL", 0x555666L << 2}),
};
//...
But there is a probem. This does not compile as the compiler (preprocessor) gets confused by the commas between the structure members.
The comma in the passed structure members is seen by the macro and it thinks there are extra parameters.
GCC says you can put brackets round terms where you want to keep the commas, like this
MACRO((keep, the, commas))
e.g. In this case, that would be
CREATE_EXTRA("DATA1", 1, ({"IO_TBL", 0x123456L}) )
But that would not work with a structure as we'd get
.xtra = ({"IO_TBL", 0x123456L})
Which is not a valid initaliser.
The other option would be
CREATE_EXTRA("DATA1", 1, {("IO_TBL", 0x123456L)} )
Which results in
.xtra = {("IO_TBL", 0x123456L)}
Which is also not valid
And if we put the braces inside the macro
.xtra = {EXTRA}
...
CREATE_EXTRA("DATA1", 1, ("IO_TBL", 0x123456L) )
We get the same
Obviously some might say "just pass the elements of XTRA one at a time".
Remember this is a simple, very cut down, example and in practice doing that would lose information and make the code much harder to understand, it would be harder to maintain but easer to read if the structures were just copied out longhand.
So the question is, "how to pass compound literal structures to macros as initalisers without getting tripped up by the commas between fields".
NOTE I am stuck with C11 on GCC4.8.x, so C++ or any more recent GCC is not possible.
So there is a way, though I can't find it meantioned on the GCC pages for Macros.
I found what I needed in this article: Comma omission and comma deletion
The following works.
typedef struct _array_data {
size_t size;
char * data;
}array_data_t;
#define ARRAY_DATA(ARRAY...) (char *) \
&(array_data_t) { \
sizeof((char []){ARRAY}), \
(char []){ARRAY} \
}
char * my_array = ARRAY_DATA(1,2,3,4);
size_t sent = send_packet(my_array);
if (len != my_array->size) ERROR("Not all data sent");
There are some interesting aspects to this.
1: Unlike the example in the gcc manual, the brackets are omitted round the {ARRAY}. In the document, the example uses (cast)({structure}) rather than (cast){structure}. In fact it looks like the brackets are never needed and just confuse the compiler in some cases (like when you take the address).
2: The use of the cast (char []) rather than (char *) as one would have thought to be correct.
3: Of course it makes sense but you have to put a cast round the sizeof part too, as otherwise how would it know the size of the individual literals.
For completeness, the macro in the example above expands to:
char * my_array = (char *)&(array_data_t) { \
sizeof((char []){1,2,3,4}),
(char []){1,2,3,4};
}
Any my_array is a pointer to a structure that looks like this.
* my_array = {
size_t size = 4,
char data[4] = {1,2,3,4}
}
In the following test case, the alternation of one alpha and a sequence bombs with a long error dump basically saying static assertion failed: The parser expects tuple-like attribute type. Intuitively, I expected the entire rule to produce a string but that's not what happens. I either have to change the left-side of the alternation to +alpha (making both sides vectors) or go the path of semantic actions, at least for the lone char in the alternation (append to _val). Or, change the lone left-side char_ to string. Anyways, I can't figure out what's the proper simple way of parsing a string as trivial as this, any hint is appreciated. TIA.
#include <iostream>
#include <boost/spirit/home/x3.hpp>
namespace x3 = boost::spirit::x3;
namespace grammar {
using x3::char_;
using x3::alpha;
using x3::xdigit;
const auto x =
x3::rule< struct x_class, std::string > { "x" } =
char_('/') > alpha >> *(alpha | (char_('#') > xdigit));
} // namespace grammar
int main () {
std::string input{ "/Foobar#F" }, attr;
auto iter = input.begin ();
if (phrase_parse (iter, input.end (), grammar::x, x3::space, attr)) {
std::cout << attr << std::endl;
}
return 0;
}
I hate this behaviour too. Qi was much more natural in this respect.
I honestly don't always know how to "fix" it although
in this case it seems you can use raw[] - simplifying the grammar as well
sometimes it helps to avoid mixing operator> and operator>>
Here's what I'd do for your grammar:
Live On Coliru
#include <iostream>
#include <boost/spirit/home/x3.hpp>
namespace x3 = boost::spirit::x3;
namespace grammar {
const auto x =
x3::rule<struct x_class, std::string> { "x" } =
x3::raw [ '/' > x3::alpha >> *(x3::alpha | ('#' > x3::xdigit)) ];
}
int main () {
std::string input{ "/Foobar#F" }, attr;
auto iter = input.begin ();
if (phrase_parse (iter, input.end (), grammar::x, x3::space, attr)) {
std::cout << attr << std::endl;
}
}
Prints
/Foobar#F
I have read here that "C/C++ constants are installed as global Tcl variables containing the appropriate value", which applies to enum as well. I am trying to build a Tcl wrapper using swig for an enum class (called "Statement") that will result into the corresponding Tcl variables to be stored as string objects. The C++ code provides some ostream conversion facilities that I thought I could use to perform the conversion, but I cannot find a recipe that will work. I have tried the following:
//%typemap(argout) Statement *out {
// ostringstream oss;
// oss << $1;
// $result = Tcl_NewStringObj(oss.str()->c_str(), oss.str().size());
//}
//%typemap(constcode) Statement {
// ostringstream oss;
// oss << $1;
// $result = Tcl_NewStringObj(oss.str()->c_str(), oss.str().size());
//}
//%typemap(out) Statement {
// ostringstream oss;
// oss << $1;
// $result = Tcl_NewStringObj(oss.str()->c_str(), oss.str().size());
//}
Another (maybe related issue) is that no Tcl variables are created at all from the enums in my wrapper. I read from this follow up link that when you use static linking, the Tcl variables used to store constants will be put in the ::swig namespace. But this is not my problem here: I do not have a ::swig namespace, and info vars does not list any variables in the top namespace either.
I have found the answer for the second issue. My wrapper SWIG code uses the %init directive which uses some magic to make use of the readline library. It was evaluating a Tcl script that starts the readline command processing loop before the rest of the application initialization got a chance to complete. The constant initialization code was generated after the block of code provided to the %init SWIG block, so it never got executed. By moving the SWIG declaration of the enum above the %init section, the relative order of inserted constant initialization code and %init segment was changed, and the issue was solved.
Bottom-line: the relative order of declaration and %init segment in your SWIG wrapper code matters.
I was able to resolve this using a typemap of the form:
%typemap(out) enum NS::Statement {
ostringstream oss;
oss << "NS_Statement(" << $1 << ")";
Tcl_SetObjResult(interp,Tcl_NewStringObj(oss.str().c_str(), oss.str().size()));
}
The reason it was not working previously is that the enum is defined within a namespace statement. Even though I had 'using namespace NS;' statement before the typemap declaration, it was not being applied until I provided the full namespace qualifier of the enum. Also, both typemap statements had to be provided before the wrapper code declaring the enum constants.
As you can see, the variable name that is returned is a Tcl array variable name. In order for things to be consistent, the global variables set by the generated code that contain the actual values of the enum need also be changed. I was able to achieve that using another typemap like so:
%typemap(constcode,noblock=1) int {
%set_constant("NS_Statement($symname)", SWIG_From_long(static_cast< int >($1)));
}
In the case where you need to wrap multiple enum type, just insert a similar set of typemaps for each enum before its SWIG declaration, matching the Tcl array name part with the enum type being mapped. In case where there are non-enum constants to be declared in your SWIG code, or other enum types that you do not want to wrap in that way, add a last typemap(constcode) to reset to default behavior before you add the SWIG code declaring these other constants.
I have created a small example that illustrates that approach:
// file example.h
enum TOPETYPE {BI, DUL, BUC};
class MyClass {
public:
enum ETYPE {ONE,TWO, THREE};
static void Foo(ETYPE);
static ETYPE Bar(int);
};
namespace NS {
enum LIBENUM {LIB1, LIB2, LIB3};
}
extern const char * ETYPE2Str(MyClass::ETYPE);
extern const char * TOPETYPE2Str(TOPETYPE);
extern const char * LIBENUM2Str(NS::LIBENUM);
/* File : example.i */
%module example
%{
#include "example.h"
#include <sstream>
using namespace std;
%}
#define XX 0
%typemap(out) enum TOPETYPE {
#include <iostream>
std::ostringstream oss;
oss << "TOPETYPE(" << TOPETYPE2Str($1) << ")";
Tcl_SetObjResult(interp,Tcl_NewStringObj(oss.str().c_str(), -1));
}
%typemap(constcode,noblock=1) int {
%set_constant("TOPETYPE($symname)", SWIG_From_long(static_cast< int >($1)));
}
enum TOPETYPE {BI, DUL, BUC};
%typemap(out) enum MyClass::ETYPE {
#include <iostream>
std::ostringstream oss;
oss << "MyClass_ETYPE(MyClass_" << ETYPE2Str($1) << ")";
Tcl_SetObjResult(interp,Tcl_NewStringObj(oss.str().c_str(), -1));
}
%typemap(constcode,noblock=1) int {
%set_constant("MyClass_ETYPE($symname)", SWIG_From_long(static_cast< int >($1)));
}
class MyClass {
public:
enum ETYPE {ONE,TWO, THREE};
static void Foo(ETYPE);
static ETYPE Bar(int);
};
%typemap(out) enum NS::LIBENUM {
#include <iostream>
std::ostringstream oss;
oss << "NS_LIBENUM(" << LIBENUM2Str($1) << ")";
Tcl_SetObjResult(interp,Tcl_NewStringObj(oss.str().c_str(), -1));
}
%typemap(constcode,noblock=1) int {
%set_constant("NS_LIBENUM($symname)", SWIG_From_long(static_cast< int >($1)));
}
namespace NS {
enum LIBENUM {LIB1, LIB2, LIB3};
}
// file example.cpp
#include "example.h"
#include <iostream>
using namespace std;
void MyClass::Foo(MyClass::ETYPE typ)
{
cout << "Enum value = " << typ << endl;
}
MyClass::ETYPE MyClass::Bar(int val)
{
switch (static_cast<MyClass::ETYPE>(val)) {
case MyClass::ETYPE::ONE: {return MyClass::ETYPE::ONE;}
case MyClass::ETYPE::TWO: {return MyClass::ETYPE::TWO;}
case MyClass::ETYPE::THREE: {return MyClass::ETYPE::THREE;}
default: {return MyClass::ETYPE::THREE;}
}
}
const char * ETYPE2Str(MyClass::ETYPE val) {
switch (val) {
case MyClass::ETYPE::ONE: {return "ONE";}
case MyClass::ETYPE::TWO: {return "TWO";}
case MyClass::ETYPE::THREE: {return "THREE";}
default: {return "unknown";}
}
}
const char * TOPETYPE2Str(TOPETYPE val) {
switch (val) {
case TOPETYPE::BI: {return "BI";}
case TOPETYPE::DUL: {return "DUL";}
case TOPETYPE::BUC: {return "BUC";}
default: {return "unknown";}
}
}
const char * LIBENUM2Str(NS::LIBENUM val) {
switch (val) {
case NS::LIB1: {return "LIB1";}
case NS::LIB2: {return "LIB2";}
case NS::LIB3: {return "LIB3";}
default: {return "unknown";}
}
}
You can try this out with:
swig -c++ -tcl8 example.i
g++ -c -fpic example_wrap.cxx example.cpp -I/usr/local/include
g++ -shared example.o example_wrap.o -o example.so
And then, inside tclsh:
% load example4.so
% info vars
XX tcl_rcFileName tcl_version argv0 argv tcl_interactive auto_path errorCode NS_LIBENUM errorInfo auto_execs auto_index env tcl_pkgPath MyClass_ETYPE TOPETYPE tcl_patchLevel swig_runtime_data_type_pointer4 argc tcl_library tcl_platform
% info commands
MyClass_Bar tell socket subst open eof pwd glob list pid exec auto_load_index time unknown eval lassign lrange fblocked lsearch auto_import gets case lappend proc break variable llength auto_execok return linsert error catch clock info split array if fconfigure concat join lreplace source fcopy global switch auto_qualify update close cd for auto_load file append lreverse format unload read package set binary namespace scan delete_MyClass apply trace seek while chan flush after vwait dict continue uplevel foreach lset rename fileevent regexp new_MyClass lrepeat upvar encoding expr unset load regsub history interp exit MyClass puts incr lindex lsort tclLog MyClass_Foo string
% array names NS_LIBENUM
LIB1 LIB2 LIB3
% array names MyClass_ETYPE
MyClass_TWO MyClass_ONE MyClass_THREE
% array names TOPETYPE
DUL BUC BI
% puts $XX
0
% MyClass_Bar $MyClass_ETYPE(MyClass_ONE)
MyClass_ETYPE(MyClass_ONE)
% MyClass_Foo $MyClass_ETYPE(MyClass_ONE)
Enum value = 0
% exit
I just discovered that gcc and clang++, would let me use a const int for the underlying type of an enum. I wonder if that has any utility of if for all purposes it is the same as having an enum based on int.
I thought that may be it would make the enum instance not assignable, but it was not the case. (And to be honest, I thought it would not compile in the same way that you can't make a class derived from a const base type class C2 : const C1{})
Is there any use or subtle difference between enum : int and enum : const int? If no, why would the compiler allow it?
Example:
#include<iostream>
enum A : const int{ // is this the same as enum A : int ?
no = 0,
si
};
int main(){
A a;
a = si; // is asignable
a = no; // twice
std::cout << (int)a << std::endl; // prints '0'
return 0;
}
Funny that I can do this enum A : volatile int as well.
(Fortunately, I can't do this enum A : int& or enum A : int*.)
For completeness sake, here is the relevant standard quote (from C++11 up to latest draft):
[dcl.enum]#2 The enumeration type [...] The type-specifier-seq of an enum-base shall name an integral type; any cv-qualification is ignored.
where type-specifier-seq is the underlying type specification in the corresponding grammar production.
There seems to be no difference - the underlaying type for both is int. Here some example test program:
#include <iostream>
#include <type_traits>
enum e1 : int {};
enum e2: const int {};
int main() {
bool e1_type = std::is_same<
const int
,typename std::underlying_type<e1>::type
>::value;
bool e2_type = std::is_same<
const int
,typename std::underlying_type<e2>::type
>::value;
std::cout
<< "underlying type for 'e1' is " << (e1_type?"const":"non-const") << '\n'
<< "underlying type for 'e2' is " << (e2_type?"const":"non-const") << '\n';
}
https://wandbox.org/permlink/dXLDe80zKhSxglcl
I have the following construct:
template <class... Args>
class some_class
{
public:
some_class() = default;
some_class(Args...) = delete;
~some_class() = default;
};
template<>
class some_class<void>
{
public:
some_class() = default;
~some_class() = default;
};
The reason for this is that I just want to allow the users to create objects using the default constructor, so for example:
some_class<int,float> b;
should work but
some_class<int,float> c(1,3.4);
should give me a compilation error.
At some point in time I also needed to create templates based on void hence, the specialization for void:
some_class<void> a;
But by mistake I have typed:
some_class<> d;
And suddenly my code stopped compiling and it gave me the error:
some_class<Args>::some_class(Args ...) [with Args = {}]’ cannot be
overloaded
some_class(Args...) = delete;
So here comes the question: I feel that I am wrong that I assume that some_class<> should be deduced to the void specialization... I just don't know why. Can please someone explain why some_class<> (ie: empty argument list) is different from some_class<void>? (A few lines from the standard will do :) )
https://ideone.com/o6u0D6
void is a type like any other (an incomplete type, to be precise). This means it can be used as a template argument for type template parameters normally. Taking your class template, these are all perfectly valid, and distinct, instantiations:
some_class<void>
some_class<void, void>
some_class<void, void, void>
some_class<void, char, void>
In the first case, the parameter pack Args has one element: void. In the second case, it has two elements: void and void. And so on.
This is quite different from the case some_class<>, in which case the parameter pack has zero elements. You can easily demonstrate this using sizeof...:
template <class... Pack>
struct Sizer
{
static constexpr size_t size = sizeof...(Pack);
};
int main()
{
std::cout << Sizer<>::size << ' ' << Sizer<void>::size << ' ' << Sizer<void, void>::size << std::endl;
}
This will output:
0 1 2
[Live example]
I can't really think of a relevant part of the standard to quote. Perhaps this (C++11 [temp.variadic] 14.5.3/1):
A template parameter pack is a template parameter that accepts zero or more template arguments. [ Example:
template<class ... Types> struct Tuple { };
Tuple<> t0; // Types contains no arguments
Tuple<int> t1; // Types contains one argument: int
Tuple<int, float> t2; // Types contains two arguments: int and float
Tuple<0> error; // error: 0 is not a type
—end example ]