Is there a way to specify the default constructor of an enum class
?
I am using an enum class
to specify a set of values which are allowable for
A type defined with enum class
or enum struct
is not a a class but a scoped enumeration and can not have a default constructor defined. The C++11 standard defines that your PinID pid = PinID();
statement will give a zero-initialization. Where PinID
was defined as a enum class
. It also allows enum types in general to hold values other than the enumerator constants.
To understand that PinID() gives zero initialization requires reading standard sections 3.9.9, 8.5.5, 8.5.7 and 8.5.10 together:
8.5.10 - An object whose initializer is an empty set of parentheses, i.e., (), shall be value-initialized
8.5.7 - To value-initialize an object of type T means:
... otherwise, the object is zero-initialized.
8.5.5 - To zero-initialize an object or reference of type T means: — if T is a scalar type (3.9), the object is set to the value 0 (zero), taken as an integral constant expression, converted to T;
3.9.9 - States that enumeration types are part of the set of types known as scalar types.
A possible solution:
To meet your points 1 to 5 you could write a class along the lines of:
class PinID
{
private:
PinID(int val)
: m_value(val)
{}
int m_value;
public:
static const PinID N4;
static const PinID N17;
/* ...etc... */
PinID()
: m_value(N4.getValue())
{}
PinID(const PinID &id)
: m_value(id.getValue())
{}
PinID &operator = (const PinID &rhs)
{
m_value = rhs.getValue();
return *this;
}
int getValue() const
{
return m_value;
}
// Attempts to create from int and throw on failure.
static PinID createFromInt(int i);
friend std::istream& operator>>(std::istream &is, PinID &v)
{
int candidateVal(0);
is >> candidateVal;
v = PinID::createFromInt(candidateVal);
return is;
}
};
const PinID PinID::N4 = PinID(4);
/* ...etc... */
That can give you something that you would have to make specific efforts to get an invalid values into. The default constructor and stream operator should allow it to work with lexical_cast.
Seems it depends how critical the operations on a PinID are after it's creation whether it's worth writing a class or just handling the invalid values everywhere as the value is used.
An enum class
is just a strongly-typed enum
; it's not a class
. C++11 just reused the existing class
keyword to avoid introducing a new keyword that would break compatibility with legacy C++ code.
As for your question, there is no way to ensure at compile time that a cast involves a proper candidate. Consider:
int x;
std::cin >> x;
auto p = static_cast<PinID>(x);
This is perfectly legal and there is no way to statically ensure the console user has done the right thing.
Instead, you will need to check at runtime that the value is valid. To get around this in an automated fashion, one of my co-workers created an enum
generator that builds these checks plus other helpful routines given a file with enumeration values. You will need to find a solution that works for you.
I know that this question is dated and that it already has an accepted answer but here is a technique that might help in a situation like this with some of the newer features of C++
You can declare this class's variable either non static
or static
, it can be done in several ways permitted on support of your current compiler.
Non Static:
#include <iostream>
#include <array>
template<unsigned... IDs>
class PinIDs {
private:
const std::array<unsigned, sizeof...(IDs)> ids { IDs... };
public:
PinIDs() = default;
const unsigned& operator[]( unsigned idx ) const {
if ( idx < 0 || idx > ids.size() - 1 ) {
return -1;
}
return ids[idx];
}
};
Static: - There are 3 ways to write this: (First One - C++11 or 14 or higher) last 2 (c++17).
Don't quote me on the C++11 part; I'm not quite sure when variadic templates or parameter packs were first introduced.
template<unsigned... IDs>
class PinIDs{
private:
static const std::array<unsigned, sizeof...(IDs)> ids;
public:
PinIDs() = default;
const unsigned& operator[]( unsigned idx ) const {
if ( idx < 0 || idx > ids.size() - 1 ) {
return -1;
}
return ids[idx];
}
};
template<unsigned... IDs>
const std::array<unsigned, sizeof...(IDs)> PinIDs<IDs...>::ids { IDs... };
template<unsigned... IDs>
class PinIDs{
private:
static constexpr std::array<unsigned, sizeof...(IDs)> ids { IDs... };
public:
PinIDs() = default;
const unsigned& operator[]( unsigned idx ) const {
if ( idx < 0 || idx > ids.size() - 1 ) {
return -1;
}
return ids[idx];
}
};
template<unsigned... IDs>
class PinIDs{
private:
static inline const std::array<unsigned, sizeof...(IDs)> ids { IDs... };
public:
PinIDs() = default;
const unsigned& operator[]( unsigned idx ) const {
if ( idx < 0 || idx > ids.size() - 1 ) {
return -1;
}
return ids[idx];
}
};
All examples above either non-static or static work with the same use case below and provide the correct results:
int main() {
PinIDs<4, 17, 19> myId;
std::cout << myId[0] << " ";
std::cout << myId[1] << " ";
std::cout << myId[2] << " ";
std::cout << "\nPress any key and enter to quit." << std::endl;
char c;
std::cin >> c;
return 0;
}
Output
4 17 19
Press any key and enter to quit.
With this type of class template using a variadic parameter list, you don't have to use any constructor but the default. I did add bounds checking into the array so that the operator[]
doesn't exceed bounds of its size; I could of threw an error but with unsigned
type I just simply returned -1 as an invalid value.
With this type, there is no default as you have to instantiate this kind of object via template parameter list with a single or set of values. If one wants to they can specialize this class
with a single parameter of 0
for a default type. When you instantiate this type of object; it is final as in it can not be changed from its declaration. This is a const object and still holds to be default constructible.