Inner enum classes of templates not considered constant expressions? - c++

I'm using C++ with GCC's C extension for designated inits / designated array initializers and I've come across a bit of a pickle. If I have the following (contrived) example code,
#include <string>
#include <string_view>
struct STRUCTURE {
enum class ENUM { A, B, C };
static constexpr std::string_view STRINGS[] {
[static_cast<int>(ENUM::A)] = "A",
[static_cast<int>(ENUM::B)] = "B",
[static_cast<int>(ENUM::C)] = "C"
};
constexpr std::string_view operator[](ENUM e) const {
return STRINGS[static_cast<int>(e)];
}
};
Everything works as expected. However, if I now template the structure, i.e.
#include <string>
#include <string_view>
template <typename T>
struct STRUCTURE { /*same structure definition*/ };
I get a compile-time error that the C99 designator initializers I have are not integral constant expressions.
In fact, in some cases with namespaces I get an internal compiler error / internal compiler segfault, leading me to believe this is an internal issue, but I cannot confirm such.
If I move the scoped enum class outside of the template, everything works again. In that manner, I can simply add a level of indirection to my actual code, however I would prefer not doing so.
Here is a godbolt with a macro allowing you to change between the templated and non-templated definition, and a simple main printing out one of the strings.
Even odder: Say I change the type of the internal array to ints. I.e,
struct STRUCTURE {
enum class ENUM { A, B, C };
static constexpr int INTS[] {
[static_cast<int>(ENUM::A)] = 65,
[static_cast<int>(ENUM::B)] = 66,
[static_cast<int>(ENUM::C)] = 67
};
constexpr int operator[](ENUM e) const {
return INTS[static_cast<int>(e)];
}
};
I have the same issue. But now if I assign the values via static casts of the enum as the designators are, i.e,
...
static constexpr int INTS[] {
[static_cast<int>(ENUM::A)] = static_cast<int>(ENUM::A),
[static_cast<int>(ENUM::B)] = static_cast<int>(ENUM::B),
[static_cast<int>(ENUM::C)] = static_cast<int>(ENUM::C)
};
...
This works, template or not. Godbolt for this, too.

Related

Curious behaviour with static_assert and CRTP

While developing a protocol library, I used some preventive static_assert to check whether some different arrays had the same length.
At first, I started using them in all derived classes, but at some moment, I wondered why not to put that protection in base class (the CRTP base class) and save in typewording. I did it, but I then found that it was not possible to do as I expected and I would like to know: 1st/ why? and 2nd/ is it fixable in some way?
The minimal working example of original source code can be this:
#include <stdio.h>
#include <iostream>
#include <array>
template<typename T>
struct skill
{
static constexpr void some_function() { static_assert(T::vars.size() == T::bits.size()); }//This is OK
static_assert(T::vars.size() == T::bits.size());//This one arises an error!!!
private:
skill() = default;
friend T;
};
struct message1 : skill<message1>
{
static constexpr std::array vars{ "id", "len", "source", "destination" };
static constexpr std::array bits{ 8, 10, 12, 12 };
static_assert(vars.size() == bits.size());//This is OK
};
struct message2 : skill<message2>
{
static constexpr std::array vars{ "id", "len", "command" };
static constexpr std::array bits{ 8, 10, 24 };
static_assert(vars.size() == bits.size());//This is OK
};
int main()
{
message1::some_function();
}
Please, note static_assert in struct skill, which is the one arisen the next error: "error: incomplete type 'message1' used in nested name specifier" in g++.
The link to source code in coliru is: https://coliru.stacked-crooked.com/a/24a1916a5055949a
Please, any help with this?

Determine whether a type exists without feature-test macro

I'd like to determine whether a type exists without using a feature-test macro. Here's the idea using a macro:
namespace real
{
struct foo final { static constexpr const char* name = "real::foo"; };
}
#define real_foo_ 314
struct my_foo final { static constexpr const char* name = "my_foo"; };
namespace real
{
#if !real_foo_
using foo = my_foo;
#endif
}
That is, real::foo should to the "real" foo if it exists, otherwise my_foo will be used; subsequent code uses real::foo w/o knowing or caring whether it's the actual version or the replacement.
Achiving the same template meta-programming seems to be the right idea:
#include <type_traits>
namespace real
{
struct bar final { static constexpr const char* name = "real::bar"; };
}
struct my_bar final { static constexpr const char* name = "my_bar"; };
// https://devblogs.microsoft.com/oldnewthing/20190710-00/?p=102678
template<typename, typename = void>
constexpr bool is_type_complete_v = false;
template<typename T>
constexpr bool is_type_complete_v<T, std::void_t<decltype(sizeof(T))>> = true;
namespace real { struct bar; }
namespace real
{
using bar = std::conditional_t<is_type_complete_v<real::bar>, real::bar, my_bar>;
}
The above works as shown:
#include <iostream>
int main()
{
real::foo foo;
std::cout << foo.name << "\n";
real::bar bar;
std::cout << bar.name << "\n";
}
But removing the actual definition (not the forward) of real::bar causes compiler errors:
namespace real
{
//struct bar final { static constexpr const char* name = "real::bar"; };
}
error C2371: 'real::bar': redefinition; different basic types
message : see declaration of 'real::bar'
error C2079: 'bar' uses undefined struct 'real::bar'
Is there a way to make real::bar work like real::foo without relying on a feature-test macro? Note that as with real::foo, the name of real::bar can't be changed (e.g., to real::really_bar).
(Actual use case: C++14/C++17/C++20 library features implemented in C++11; once client code using std::filesystem::path has been written, it shouldn't have to change.)
Compiler complains on your code because you are trying to create two entities with same name. Consider changing
namespace real
{
struct bar final { static constexpr const char* name = "real::bar"; };
}
to something like
namespace real
{
struct absolutely_bar final { static constexpr const char* name = "real::bar"; };
}
...
namespace real { struct absolutely_bar; }
namespace real
{
using bar = std::conditional_t<is_type_complete_v<real::absolutely_bar>, real::absolutely_bar, my_bar>;
}
PS: creating such aliases is usually a bad pattern since it's not obvious.

Enum in a class with strings

I'm trying to implement a class (C++) with an enum (with the permitted parameters). I got a working solution, but if I try to extend the functionality I get stuck.
Header data_location.hpp
class DataLocation
{
private:
public:
enum Params { model, period };
std::string getParamString(Params p);
};
Program data_location.cpp
string DataLocation::getParamString(Params p){
static const char * ParamsStrings[] = {"MODEL", "PERIOD"};
return ParamsStrings[p];
}
The array ParamsStrings should be generally available in the class, because I need a second method (with inverse function) returning the enum value given a string.
If I try to define the array in the header I get the error:
in-class initialization of static data member ‘const char* DataLocation::ParamsStrings []’ of incomplete type
Why is the type incomplete? The compiler is for sure able to counts the strings in the array, isn't it?
In case there is no way to get my code working, is there an other way? With 1) no XML; 2) no double definition of the strings; 3) not outside the class; 4) no in code programmed mapping.
In class (header) use keyword static and initialize it outside (.cpp) without the static keyword:
class DataLocation {
public:
enum Params { model, period };
string getParamString(Params p);
static const char* ParamsStrings[];
// ^^^^^^
};
const char* DataLocation::ParamsStrings[] = {"MODEL", "BLLBLA"};
//^^^^^^^^^^^^^^^^^^^^^^^^
The code you have posted is perfectly fine.
Here's the proof:
#include <iostream>
#include <string>
struct DataLocation
{
enum Params { model, period };
std::string getParamString(Params p){
static const char * ParamsStrings[] = {"MODEL", "PERIOD"};
return ParamsStrings[p];
}
};
int main()
{
auto a = DataLocation();
std::cout << a.getParamString(DataLocation::model) << std::endl;
return 0;
}
The error message you are getting is not to do with definition of a static data member in an inline function - that's allowed.
There's something else you're not showing us.
The main issue in my question (the second part) was that if I split the class in .hpp and .cpp the definition of the array (I mixed *char and string) has also to be split:
// data_location.hpp
class DataLocation {
static const char * ParamsStrings[];
}
// data_location.cpp
const char * ParamsStrings[] = {"MODEL", "PERIOD"};
At the end I introduced a consistency check to be sure that the number of values in enum growths as the number of strings. Because the array in C++ is somehow limited I had to go for a std::vector (to get the size).
Code for data_location.hpp
#ifndef DATA_LOCATION_HPP_
#define DATA_LOCATION_HPP_
#include <string>
#include "utils/dictionary.hpp"
extern const char* ENV_DATA_ROOT;
struct EDataLocationInconsistency : std::runtime_error
{
using std::runtime_error::runtime_error;
};
struct EDataLocationNotValidParam : std::runtime_error
{
using std::runtime_error::runtime_error;
};
class DataLocation
{
private:
std::string mRootLocation;
static const std::vector<std::string> msParamsStrings;
static bool msConsistenceCheckDone;
public:
DataLocation();
std::string getRootLocation();
std::string getLocation(Dictionary params);
enum Params { model, period, LAST_PARAM};
std::string Param2String(Params p);
Params String2Param(std::string p);
};
#endif
Code for data_location.cpp
#include "data_location.hpp"
#include <string>
#include <cstdlib>
using namespace std;
const char* ENV_DATA_ROOT = "DATA_ROOT";
bool DataLocation::msConsistenceCheckDone = false;
DataLocation::DataLocation() {
mRootLocation = std::getenv(ENV_DATA_ROOT);
if (not msConsistenceCheckDone) {
msConsistenceCheckDone = true;
if (LAST_PARAM+1 != msParamsStrings.size()) {
throw(EDataLocationInconsistency("DataLocation: Check Params and msParamsStrings"));
}
}
}
string DataLocation::getRootLocation() {
return mRootLocation;
}
string DataLocation::getLocation(Dictionary params) {
// to do
return "";
}
const vector<string> DataLocation::msParamsStrings = { "MODEL", "PERIOD", ""};
string DataLocation::Param2String(Params p) {
if (p>=msParamsStrings.size()) {
throw(EDataLocationNotValidParam("Parameter not found"));
}
return msParamsStrings[p];
}
DataLocation::Params DataLocation::String2Param(string p) {
for (int i = 0; i < msParamsStrings.size(); i++) {
if (p == msParamsStrings[i])
return (Params)i;
}
throw(EDataLocationNotValidParam("Parameter not found"));
}
And also a unit test:
#include <boost/test/unit_test.hpp>
#include "data_location.hpp"
#include <string>
using namespace std;
BOOST_AUTO_TEST_SUITE( data_location )
BOOST_AUTO_TEST_CASE(data_location_1) {
DataLocation dl;
auto s = dl.getRootLocation();
BOOST_CHECK_EQUAL(s, "/home/tc/data/forex" );
BOOST_CHECK_EQUAL(dl.Param2String(DataLocation::period),"PERIOD");
BOOST_CHECK_EQUAL(dl.String2Param("PERIOD"),DataLocation::period);
BOOST_CHECK_THROW(dl.String2Param("SOMETHING"), EDataLocationNotValidParam);
BOOST_CHECK_THROW(dl.Param2String((DataLocation::Params)100), EDataLocationNotValidParam);
}
BOOST_AUTO_TEST_SUITE_END()
C++ is very picky about what it will let you initialize inside of a class definition; there are some particularly non-intuitive rules surrounding static members. It all has to do with the ODR, and why all the rules are the way they are is not especially important.
To cut to the chase, making your array a static constexpr const member should shut the compiler up. With the C++11 standard, the restrictions were relaxed a bit, and one of the new stipulations was that static constexpr members can be initialized inline. This is perfect for your application, since the strings in your array are compile-time constants.
The recent g++ compiler which support C++0x or later compiles thus code. Pure C compile compiles, too. Because strings in initialization like {"MODEL", "PERIOD"}; implemented as const char * pointer to the char array.

Import c enum to c++ class definition

I have a class like this:
class TType {
public:
...
enum binary_type {
bt_a = 0,
bt_xyz,
....
bt_ak = 10,
....
};
}
and I use it in several places, also the enum:
if(var12 == TType::bt_a ) { ....
Now I imported a C library which has exactly the same enum (same keys, same values, same size) inside one of it's headerfiles:
typedef enum data_types_e {
bt_a = 0,
bt_xyz,
....
} data_types;
How can I define the enum in the c++ class definition to use the declaration of the c headerfile?
I want to continue using the enum the same way as before (TType::bt_a), and avoid copying the whole enum. Furthermore I don't wont to modify the library (otherwise a preprocessor-macro would do the trick) and I want changes made in the library also be made to the enum in my class.
Neither a typedef inside the c++ class definition nor a type alias (c++11) seem to work in this situation.
"How can I define the enum in the c++ class definition to use the declaration of the c headerfile?"
You can simply reuse the values from the c-style enum:
#include "TheOtherEnum.h"
...
enum binary_type {
bt_a = ::bt_a,
bt_xyz = ::bt_xyz,
....
bt_ak = ::bt_ak,
....
};
"Neither a typedef inside the c++ class definition nor a type alias (c++11) seem to work in this situation."
Yes these would work to provide the correct enum type, but you'll still need to qualify the values from the global namespace and not for nested to your class.
You can make C++ enum dependant of C enum:
typedef enum data_types_e {
bt_a = 0,
bt_xyz,
....
} data_types;
// ...
class TType {
public:
...
enum binary_type {
bt_a = bt_a,
bt_xyz = bt_xyz,
....
};
}
If possible, try renaming your class TType say, class TType_1.
//=============CLibraryFileContainingEnum.h=================
typedef enum data_types_e
{
bt_a = 9999,
bt_xyz
} data_types;
//==========================================================
//==========================================================
class TType_1
{
public:
enum binary_type
{
bt_a = 8878,
bt_xyz
};
};
namespace TType
{
#include "CLibraryFileContainingEnum.h"
}
int main()
{
int a = TType::bt_a; //this prints 9999
cout << a << endl;
return 0;
}
//==========================================================
Here is my answer, this is an old topic, but better lately than never
class TType {
public :
public:
...
#undef __cplusplus
#include "yourheaderc.h"
#define __cplusplus
}
maybe with a typedef binary_type data_types to preserve your nominations
But this solution is available if your header don't contains syntax C forbidden in C++. I'm currently searching a new solution because there are prototypes in my header that contains something like :
void afunction( unsigned long id,
enum T_TYPE type,
enum T_CHAR characteristic,
unsigned short number,
unsigned short value);
because T_TYPE and T_CHAR are not typedef-ed but this syntax is non-sense in C++ because it's the declaration syntax. So my solution is not appropriate if you are in a similar case.

Need a way to have templated constants of any type in a header only library

goals:
1. store constants with their assignments in a header
2. keep constants in a namespace (without using #define)
3. allow constants to be whatever precision is needed for the app
I am trying to keep all constants for my header only library in the same namespace. I decided to do away with #defines and do things the right way, such as using:
namespace studio
{
namespace constant
{
const char* WHITE_SPACE = "\n\r\t ";
}
}
The hope is to access this like so:
studio::constant::WHITE_SPACE
This is all fine and good, from what I have gathered, doing it this way creates one of these for each translation unit, potentially to be optimized down to a single instance during linking. Even if it is not optimized like that, it is probably okay in this case.
The trouble comes when I want to add other types besides const char * as constants. For example, say I want to allow a float type (double or float) as a constant, but I would like to do it as a template so I don't have to do something like:
namespace studio
{
namespace constant
{
const char* WHITE_SPACE = "\n\r\t ";
const float PI_FLOAT = 3.141592653589793;
const double PI_DOUBLE = 3.141592653589793;
}
}
So I tried using a templated class instead of a namespace, with static functions which return the static constant like so:
namespace studio
{
template <class FloatType = float>
class constant
{
public:
static const char* white_space_chars() {
static const char* whiteSpaceChars = "\n\r\t ";
return whiteSpaceChars;
}
static const FloatType pi() {
static const FloatType _pi = 3.141592653589793;
return _pi;
}
}
}
This would then be accessed this way: studio::constant<float>::pi()
But now I have the problem where I have to supply a template parameter if I want one of my const chars even though they have nothing to do with floats, they must be accessed like so:
studio::constant<float>::white_space_chars()
because
studio::constant::white_space_chars()
does not work, even though I specified a default template parameter, apparently that only works for classes. C++ does not allow defaults on function templates.
So now the only way I can see to get around this is to have studio::constant_char, studio::constant_float, studio::constant_int, etc. That is ridiculous, right?
Really all I want is to have my defines in a namespace...
Do you know a better way to do what I am trying to do, where variables of different types can be used template like in the studio::constant namespace without forcing the user of the library to specify the type for each template parameter? Preferably not using C++11 to be more backward compatible.
Move the template onto the static methods that actually use it:
namespace studio
{
class constant
{
public:
static const char* white_space_chars() {
return "\n\r\t ";
}
template <class FloatType = float>
static const FloatType pi() {
return FloatType(3.141592653589793);
}
};
}
Then you can do this:
float flt_pi = studio::constant::pi();
double dbl_pi = studio::constant::pi<double>();
I suppose this could be done by having an override-able define such as:
#ifndef STUDIO_FLOAT_TYPE
typedef float STUDIO_FLOAT_TYPE;
#endif
namespace studio
{
namespace constant
{
const char* WHITE_SPACE = "\n\r\t ";
const STUDIO_FLOAT_TYPE PI = 3.141592653589793;
}
}
Which would allow the user to override the float type before including the library header. At least there's only one define and it still has a default.
Because surely a define like this would be used in other headers, I suppose it might be nice to keep all the customizable defines in a single header that all the individual library headers refer to.
For future reference, in C++14 you can do:
template <typename T>
const T PI = T(3.141592653589793);
See variable templates.
As an example:
// header.h
namespace constant
{
template <typename T>
constexpr T PI = T(3.141592653589793);
}
void foo();
// foo.cpp
#include <iostream>
#include "header.h"
void foo() {
std::cout << constant::PI<int> << std::endl;
}
// main.cpp
#include <iostream>
#include "header.h"
int main()
{
std::cout << constant::PI<float> << std::endl;
foo();
}
This gives the output:
3.14159
3