Creating compile-time Key-Value map in C++ - c++

I have tried to create a compile-time simple Key-Value map in C++. I'm compiling with /std:c++11.
(Using IAR compiler for embedded code and only cpp++11 is supported at the moment)
I've learnt a little bit about meta-programming.
I don't want my map to have a default value, if key is not found,
like this post: How to build a compile-time key/value store?
I want to get compiler error, if in my code I'm trying to get a value which is not stored in the map.
Here is what I've done:
#include <iostream>
template <int kk, int vv>
struct KeyValue
{
static const int k = kk, v = vv;
};
// Declaration
template <typename kv, typename...>
struct CompileTimeMap;
// Recursive Definition
template<typename kv, typename... rest>
struct CompileTimeMap<kv, rest...>
{
template<int k_input>
struct get
{
static const int val = (k_input == kv::k) ? kv::v : CompileTimeMap<rest...>::get<k_input>::val;
};
};
// Base Definition
template <typename kv>
struct CompileTimeMap<kv>
{
template<int k_input>
struct get
{
static const int val = (k_input == kv::k) ? kv::v;
};
};
// ----------------------------- Main -----------------------------
typedef CompileTimeMap<KeyValue<10, 20>, KeyValue<11, 21>, KeyValue<23, 7>> mymap;
int main()
{
// This calles should be ok !! :)
std::cout << mymap::get<10>::val << std::endl;
std::cout << mymap::get<11>::val << std::endl;
std::cout << mymap::get<23>::val << std::endl;
// This line should resolve a compile error !! (there is no key of 33)
std::cout << mymap::get<33>::val << std::endl;
}
I get the following error: error C2131: expression did not evaluate to a constant.
How can I make this work? Many thanks :)

Don't write a template metaprogram, where it is not necessary. Try this simple solution (CTMap stands for compile time map):
template <class Key, class Value, int N>
class CTMap {
public:
struct KV {
Key key;
Value value;
};
constexpr Value operator[] (Key key) const
{
return Get (key);
}
private:
constexpr Value Get (Key key, int i = 0) const
{
return i == N ?
KeyNotFound () :
pairs[i].key == key ? pairs[i].value : Get (key, i + 1);
}
static Value KeyNotFound () // not constexpr
{
return {};
}
public:
KV pairs[N];
};
constexpr CTMap<int, int, 3> ctMap {{ { 10, 20 }, { 11, 21 }, { 23, 7 } }};
static_assert (ctMap[10] == 20, "Error.");
static_assert (ctMap[11] == 21, "Error.");
static_assert (ctMap[23] == 7, "Error.");
// constexpr auto compilationError = ctMap[404];
You will get a compilation error, if you uncomment the last line (live demo). The compiler will direct you to the KeyNotFound () : line, from
which the reason of the failure should be obvious.
Remarks
The member variable pairs is made public, to make it possible to initialize the map with list-initialization.
The given N and the number of pairs that initialize CTMap should match. If N is less, you get a compilation error. If N is greater, zero-initialized pairs ({ 0, 0 }) will be silently added to pairs. Pay attention to this.
The (compiler generated) constructor does not check for duplicate keys. operator[] will find the first, but the intended usage is that you do not initialize CTMap with duplicate keys.
Recursion is not necessary in C++14. We can write a for loop in a constexpr function (live demo). The linked implementation gives another idea for giving a compiler error in case the key is not found: an exception is thrown. The member variable pairs is made private.
Intended to be used in compile time
This is a linear map, and parameters are passed by value. My intention was that the map will be used in compile time evaluated code, where this should not be a problem.
Note also that when evaluated in run time, this class won't give any feedback if the key is not found in the map.
Let's take a closer look of how ctMap[10] works in different situations. I have tried the following with three compilers (MSVC v19.24, clang 10.0.0, gcc 9.3).
constexpr int C = ctMap[10]; – The global constant C will be initialized with 20 even in debug builds. No computation is made during run-time. Note that to ensure, that the global will be created, you have to take its address somewhere. If you use the value of C, its value (20) will be substituted where it is used, and C won't be created in the object file even in debug builds.
int Foo () { return ctMap[10]; } – In debug builds operator[] will be called. In release builds MSVC inlines operator[] to Foo, i.e. eliminates one call, but the resulting code has linear complexity (the compiler is not forced to do the computation in compile time, and code optimization is poor in MSVC). Clang and gcc compiles a return 20;.
And this is how ctMap[404] works (with the same three compilers):
constexpr int C = ctMap[404]; – Does not compile, as mentioned above.
int Foo () { return ctMap[404]; } – The same remarks apply as for ctMap[10], but Foo will return 0. You cannot know, that 404 was not in the map. To get the compilation error, Foo has to be constexpr and forced to be evaluated in compile time by e.g. assigning it to a constexpr variable or an enumerator, using it in a template argument, as a size of a C array, in a static_assert, etc.

Related

Captured in-line array in constexpr variable gets lost on runtime

I'm trying to create an [int/enum]-to-text mapping class with as little overhead as possible for it's users. It's constructor should be passed a list of value-to-text mappings, which can be queried afterwards. Created objects should be constexpr and have an optional size argument, which allows the compiler to optionally check at compile time if the number of passed mappings matches what is expected. This is especially useful as an extra safety measure when used with enums -- that is: this way you can force a compile error if you neglect to add a mapping for newly added enum values. It should work with C++14 under Visual Studio 2019 and Xcode 9 and 12.
My current stab at this is the code below. However, under Visual Studio 2019 at least, the passed array of mappings is not correctly captured in the m_mappings member variable. When you run this code, m_mappings points to a random memory address, so any output you get is wrong (if it doesn't outright crash).
#include <iostream>
template <typename Type>
struct Mapping {
Type value;
const char* text;
};
template <typename Type, Type maxValue = Type(-1)>
class Mapper {
public:
template <size_t numMappings>
explicit constexpr Mapper(const Mapping<Type>(&mappings)[numMappings]) :
m_mappings(mappings),
m_numMappings(numMappings) {
static_assert(
int(maxValue) == -1 || numMappings == int(maxValue) + 1,
"Some mappings are missing!"
);
}
const char* Map(Type value) const {
for (size_t mappingNr = 0; mappingNr < m_numMappings; ++mappingNr)
if (m_mappings[mappingNr].value == value)
return m_mappings[mappingNr].text;
return "?";
}
private:
const Mapping<Type>* m_mappings;
const size_t m_numMappings;
};
enum class TestEnum {
a,
b,
c,
maxValue = c
};
int main() {
constexpr Mapper<int> intMapper_noCheck({
{ 11, "a" },
{ 5, "b" },
{ 26, "x" }
});
std::cout << intMapper_noCheck.Map(10);
constexpr Mapper<int, 3> intMapper_check({
{ 0, "z" },
{ 1, "f" },
{ 2, "t" },
{ 3, "#" }
});
std::cout << intMapper_check.Map(2);
// if we'd pass e.g. TestEnum::b here, we get a nice compile time error
constexpr Mapper<TestEnum, TestEnum::maxValue> enumMapper({
{ TestEnum::a, "-" },
{ TestEnum::b, "-" },
{ TestEnum::c, "+" }
});
std::cout << enumMapper.Map(TestEnum::b);
// expected output by now: "?t-"
std::cin.get();
return 0;
}
A possible solution is to capture the mapping array in a separate constexpr variable, and pass that to the mapper objects, like so:
constexpr Mapping<TestEnum> enumMapping[] = {
{ TestEnum::a, "-" },
{ TestEnum::b, "-" },
{ TestEnum::c, "+" }
};
constexpr Mapper<TestEnum, TestEnum::maxValue> enumMapper(enumMapping);
std::cout << enumMapper.Map(TestEnum::b);
This way the mappings do get preserved and the output is correct. However, I find this extra layer makes it much more 'messy'...
The complicating factor here is that the passed in array's size must be captured in a constexpr-friendly way, and I do not want to have to specify it separately by hand.
Using a fixed size array with the size specified in the constructor's template argument is one way of doing it, but when passing an array in-line it is thus not accessible at runtime.
Another version would be to pass (and store) a std::initializer_list, but you can't static_assert on it's size method. As a workaround we could also initialize m_mappings like m_mappings(sizeMatches? mappings: throw "mismatch!"), but that way non-constexpr objects may perform nasty throws at runtime (and unfortunately the rest of the code base isn't exactly exception safe).
I contemplated using a std::array instead (that one's size is constexpr accessible), but then there is no way to pass the used size to m_mappings (the size is only known to the constructor template, and not to the class template).
I also contemplated using a template parameter pack for the constructor so users can pass the loose value-and-text args two-by-two. But then how do I stuff these into m_mappings? I'd have to e.g. make m_mappings a std::vector to do so, but that's not constexpr-compatible.
What could be another option whilst keeping it nice and clean like in my first version?
The reason it fails is that you store a pointer to the array passed to the constructor for later use.
But in your compact cases, that array no longer exists after the constructor has run. So you need to do some allocation (which might not be constexpr-friendly in all compilers) and copying. The fact that you use a char* instead of a std::string further adds to this problem - there too there is no guarantee that the pointed-to string still exists when you use it.
Also, unless this is for some sort of coursework, consider looking at the constexpr implementations of sets and maps provided by https://github.com/serge-sans-paille/frozen
After some more tampering I came up with a solution where I just copy over the passed mappings into a plain old array member. I also tried to use an std::array, but it just isn't constexpr-friendly enough in C++14.
What I tried previously was to automatically capture the mapping list size in the templated constructor (numMappings is deduced by the compiler), and then match it to the specified expected number of mappings from the class template (maxValue). But now the class template itself needs to know the number of mappings we're going to pass, so that it can reserve storage for the copy. So I've also repurposed the maxValue parameter to represent exactly that.
A drawback is thus that you now always need to count out manually how many mappings you're going to pass, which is a pain when mapping over large discontinuous int ranges where you'd really like not to care about that detail. For enums nothing really changes though, and I mainly wrote this class for handling enums.
So it's not a 100% perfect fit with the question, but it'll do I suppose... ah well.
#include <iostream>
template <typename Type>
struct Mapping {
Type value;
const char* text;
};
template <typename Type, Type maxValue>
class Mapper {
public:
template <size_t numMappings>
explicit constexpr Mapper(const Mapping<Type>(&mappings)[numMappings]) :
m_mappings{} {
constexpr int correctNumMappings{ int(maxValue) + 1 };
static_assert(numMappings <= correctNumMappings, "Too many mappings given!");
static_assert(numMappings >= correctNumMappings, "Some mappings are missing!");
for (size_t mappingNr = 0; mappingNr < numMappings; ++mappingNr)
m_mappings[mappingNr] = mappings[mappingNr];
}
const char* Map(Type value) const {
for (const Mapping<Type>& mapping : m_mappings)
if (mapping.value == value)
return mapping.text;
return "?";
}
private:
Mapping<Type> m_mappings[int(maxValue) + 1];
};
enum class TestEnum {
a,
b,
c,
maxValue = c
};
int main() {
constexpr Mapper<int, 3> intMapper_check({
{ 0, "z" },
{ 1, "f" },
{ 2, "t" },
{ 3, "#" }
});
std::cout << intMapper_check.Map(2) << "\n";
constexpr Mapper<TestEnum, TestEnum::maxValue> enumMapper({
{ TestEnum::a, "-" },
{ TestEnum::b, "-" },
{ TestEnum::c, "+" }
});
std::cout << enumMapper.Map(TestEnum::b) << "\n";
std::cin.get();
return 0;
}

Write a function that only accepts literal `0` or literal `1` as argument

Sometimes for algebraic types it is convenient to have a constructor that takes a literal value 0 to denote the neutral element, or 1 to denote the multiplicative identity element, even if the underlying type is not an integer.
The problem is that it is not obvious how to convince the compiler only to accept, 0 or 1 without accepting any other integer.
Is there a way to do this in C++14 or beyond, for example combining literals, constexpr or static_assert?
Let me illustrate with a free function (although the idea is to use the technique for a constructor that take a single argument. Contructors cannot take template parameters either).
A function that accepts zero only could be written in this way:
constexpr void f_zero(int zero){assert(zero==0); ...}
The problem is that, this could only fail at runtime. I could write f_zero(2) or even f_zero(2.2) and the program will still compile.
The second case is easy to remove, by using enable_if for example
template<class Int, typename = std::enable_if_t<std::is_same<Int, int>{}> >
constexpr void g_zero(Int zero){assert(zero==0);}
This still has the problem that I can pass any integer (and it only fails in debug mode).
In C++ pre 11 one had the ability to do this trick to only accept a literal zero.
struct zero_tag_{};
using zero_t = zero_tag_***;
constexpr void h_zero(zero_t zero){assert(zero==nullptr);}
This actually allowed one to be 99% there, except for very ugly error messages.
Because, basically (modulo Maquevelian use), the only argument accepted would be h_zero(0).
This is situation of affairs is illustrated here https://godbolt.org/z/wSD9ri .
I saw this technique being used in the Boost.Units library.
1) Can one do better now using new features of C++?
The reason I ask is because with the literal 1 the above technique fails completely.
2) Is there an equivalent trick that can be applied to the literal 1 case? (ideally as a separate function).
I could imagine that one can invent a non-standard long long literal _c that creates an instance of std::integral_constant<int, 0> or std::integral_constant<int, 1> and then make the function take these types. However the resulting syntax will be worst for the 0 case. Perhaps there is something simpler.
f(0_c);
f(1_c);
EDIT: I should have mentioned that since f(0) and f(1) are potentially completely separate functions then ideally they should call different functions (or overloads).
In C++20 you can use the consteval keyword to force compile time evaluation. With that you could create a struct, which has a consteval constructor and use that as an argument to a function. Like this:
struct S
{
private:
int x;
public:
S() = delete;
consteval S(int _x)
: x(_x)
{
if (x != 0 && x != 1)
{
// this will trigger a compile error,
// because the allocation is never deleted
// static_assert(_x == 0 || _x == 1); didn't work...
new int{0};
}
}
int get_x() const noexcept
{
return x;
}
};
void func(S s)
{
// use s.get_x() to decide control flow
}
int main()
{
func(0); // this works
func(1); // this also works
func(2); // this is a compile error
}
Here's a godbolt example as well.
Edit:
Apperently clang 10 does not give an error as seen here, but clang (trunk) on godbolt does.
You can get this by passing the 0 or 1 as a template argument like so:
template <int value, typename = std::enable_if_t<value == 0 | value == 1>>
void f() {
// Do something with value
}
The function would then be called like: f<0>(). I don't believe the same thing can be done for constructors (because you can't explicitly set template parameters for constructors), but you could make the constructor(s) private and have static wrapper functions which can be given template parameters perform the check:
class A {
private:
A(int value) { ... }
public:
template <int value, typename = std::enable_if_t<value == 0 || value == 1>>
static A make_A() {
return A(value);
}
};
Objects of type A would be created with A::make_A<0>().
Well... you have tagged C++17, so you can use if constexpr.
So you can define a literal type when 0_x is a std::integral_constant<int, 0> value, when 1_x is a std::integral_constant<int, 1> and when 2_x (and other values) gives a compilation error.
By example
template <char ... Chs>
auto operator "" _x()
{
using t0 = std::integer_sequence<char, '0'>;
using t1 = std::integer_sequence<char, '1'>;
using tx = std::integer_sequence<char, Chs...>;
if constexpr ( std::is_same_v<t0, tx> )
return std::integral_constant<int, 0>{};
else if constexpr ( std::is_same_v<t1, tx> )
return std::integral_constant<int, 1>{};
}
int main ()
{
auto x0 = 0_x;
auto x1 = 1_x;
//auto x2 = 2_x; // compilation error
static_assert( std::is_same_v<decltype(x0),
std::integral_constant<int, 0>> );
static_assert( std::is_same_v<decltype(x1),
std::integral_constant<int, 1>> );
}
Now your f() function can be
template <int X, std::enable_if_t<(X == 0) || (X == 1), bool> = true>
void f (std::integral_constant<int, X> const &)
{
// do something with X
}
and you can call it as follows
f(0_x);
f(1_x);
For the case of Ada, you can define a subtype, a new type, or a derived type that is constrained only for the values of Integer 0 and 1.
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure two_value is
-- You can use any one of the following 3 declarations. Just comment out other two.
--subtype zero_or_one is Integer range 0 .. 1; -- subtype of Integer.
--type zero_or_one is range 0 .. 1; -- new type.
type zero_or_one is new Integer range 0 .. 1; -- derived type from Integer.
function get_val (val_1 : in zero_or_one) return Integer;
function get_val (val_1 : in zero_or_one) return Integer is
begin
if (val_1 = 0) then
return 0;
else
return 1;
end if;
end get_val;
begin
Put_Line("Demonstrate the use of only two values");
Put_Line(Integer'Image(get_val(0)));
Put_Line(Integer'Image(get_val(1)));
Put_Line(Integer'Image(get_val(2)));
end two_value;
upon compiling you get the following warning message, although compiles successfully :
>gnatmake two_value.adb
gcc -c two_value.adb
two_value.adb:29:40: warning: value not in range of type "zero_or_one" defined at line 8
two_value.adb:29:40: warning: "Constraint_Error" will be raised at run time
gnatbind -x two_value.ali
gnatlink two_value.ali
And executing it gives the runtime error as specified by the compiler
>two_value.exe
Demonstrate the use of only two values
0
1
raised CONSTRAINT_ERROR : two_value.adb:29 range check failed
So, basically you can constrain the values by defining the new types, derived types or subtypes, you don't need to include the code to check the range, but based on your data type the compiler will automatically warn you.
This isn't a modern solution, but adding on to Zach Peltzer's solution, you can keep your syntax if you use macros...
template <int value, typename = std::enable_if_t<value == 0 | value == 1>>
constexpr int f_impl() {
// Do something with value
return 1;
}
#define f(x) f_impl<x>()
int main() {
f(0); //ok
f(1); //ok
f(2); //compile time error
}
Though, with the constructor problem you could just make the class templated instead of trying to have a templated constructor
template<int value, typename = std::enable_if_t<value == 0 | value == 1>>
class A {
public:
A() {
//do stuff
}
};
int main() {
A<0> a0;
auto a1 = A<1>();
// auto a2 = A<2>(); //fails!
}
The best solution to accept literal 0 that I've found to date is to use std::nullptr_t as the function's input:
struct math_object
{
real x,y,z;
math_object(std::nullptr_t) : x(0), y(0), z(0) {}
};
This has conversion advantages over some of the other solutions. For example, it allows syntax such as.. void MyFunc(const math_object &obj=0); I've been using this for years, and haven't found any trouble. However, I do not have a similar solution for literal 1. For that, I created a construct::id structure that has a global IDENTITY variable.
There's a basic problem. How can you do that in the compiler to be done for a parameter, and at the same time be efficient? Well, what do you need exactly?
That is included in strong typed languages like Pascal, or Ada. The enumerated types have only a couple of values, and the types are normally checked at development, but otherwise, the checks are eliminated by some compiler option at runtime, because just everything goes well.
A function interface is a contract. It is a contract between a seller (the writer of the function) and a buyer (the user of that function). There's even an arbiter, which is the programming language, that can act upon if someone tries to cheat the contract. But at the end, the program is being run in a machine that's open to make arbitraryness like modifying the set of enumerated values and put in the place a completely (and not permitted value).
The problem comes also with separate compilation. Separate compilation has its drawbacks, as it must face a compilation, without having to recheck and retest all previous compilations you have made. Once a compilation is finished, everything you have put in the code is there. If you want the code to be efficient, then the tests are superfluous, because caller and implementer both cope with the contract, but if you want to catch a lyer, then you have to include the test code. And then, it is better to do once for all cases, or is it better to let the programmer decide when and when not we want to catch a lyer?
The problem with C (and by legacy with C++) is that they were inspired by very good programmers, that didn't mistakes, and that have to run their software in big and slow machines. They decided to make both languages (the second was for interoperability purposes) weak typed... and so they are. Have you tried to program in Ada? or Modula-2? You'll see that, over the time, the strong typing thing is more academic than otherwise, and finally what you want, as a professional, is to have the freedom to say: now I want to be safe (and include test code), and now I know what I'm doing (and please be most efficient as you can)
Conclusion
The conclussion is that you are free to select the language, to select the compiler, and to relax the rules. The compilers have the possibility to allow you that. And you have to cope with it, or invent (this is something that todays happens almost each week) your own programming language.
This is the answer to my question, based on #IlCapitano answer for a wrapper class.
This wrapper class can be made private an used only on the construction.
class Matrix {
struct ZeroOROne {
/*implicit*/ consteval ZeroOROne(int n) : val_{n} {
if (n != 0 and n != 1) {throw;} // throw can produce an error at compile time
}
int val_;
};
public:
constexpr Matrix(ZeroOROne _0_or_1) {
if(_0_or_1.val_ == 0) { // this cannot be if constexpr, but that is ok
a00 = a01 = a10 = a11 = 0.0;
new int{0}; // allocation could is ok here
} else {
a00 = a11 = 1.0;
a10 = a01 = 0.0;
new int{0}; // allocation could is ok here
}
}
double a00; double a01;
double a10; double a11;
};
In this way, only Matrix A(0) or Matrix A(1) are allowed.
(Although it works with constant variables as well, but that is ok.)
int main() {
// ZeroOROne(0);
// ZeroOROne(1);
// ZeroOROne(2); // compilation error
Matrix A(0);
Matrix B(1);
// Matrix C(2); // compilation error
int const d = 0; // this works because the compiler can "see" the 0.
Matrix D(d);
constexpr int e = 0;
Matrix E(e);
// int f = 0;
// Matrix F(f); // compile error
return B.a00;
}
Here it is shown that the "runtime" if in the constructor is not a problem and can be elided by the compiler: https://godbolt.org/z/hd6TWY6qW
The solution needs C++20 and it works in recent version of GCC and clang.

Generate new type whenever function called in C++

Is it possible to generate new type whenever a function is called?
I've read that each lambda has its own unique type, so I've tried:
template<class T, class F> struct Tag { };
template<class T>
auto func(const T &t) -> auto
{
auto f = [] () {};
return Tag<T, decltype(f)>();
}
static_assert(!std::is_same_v<decltype(func(0)), decltype(func(1))>, "type should be different.");
But, static_assert fails.
Can I make func() return a value of different type whenever func() called regardless of type T and the value of t?
No, not when the function is called. Types are generated at compile time, not at runtime.
Have a look at the question Can the 'type' of a lambda expression be expressed? Here is a code based on an answer from there.
#include <iostream>
#include <set>
int main()
{
auto n = [](int l, int r) { return l > r; };
auto m = [](int l, int r) { return l > r; };
std::set<int, decltype(n)> s(n);
std::set<int, decltype(m)> ss(m);
std::set<int, decltype(m)> sss(m);
std::cout << (std::is_same<decltype(s), decltype(ss)>::value ? "same" : "different") << '\n';
std::cout << (std::is_same<decltype(ss), decltype(sss)>::value ? "same" : "different") << '\n';
}
Result:
different
same
C++ is a statically typed language, which means the types only exist in the source code, and there is little trace of them left in the runtime.
Lambdas are no exception - they do have unique types, but those are defined at the compile time.
Templates can indeed be used to generate new types, and that is possible because templates are evaluated at compile time and therefore only exist in the source code as well.
So the strict answer is no, you cannot generate new types when the function is called, as function calls happen in runtime.
This being said, you can achieve pretty much any desirable flexibility in C++ with some clever design, just check out some common design patterns.

metaprogramming : using a const array

I'm new to metaprogramming, and I am having a const-related issue when using a tab with it.
Let's say we have several "types". Each of the types have different version, and we shall be able to handle all vesrion for each type. For that, we use a struct that contains standard info about the type, and an array containing info for each version.
The thing is, each type does not have the same number of versions. Also, the versions numbers are not very high, so I prefer not to use dynamic allocation of the said tables. But if I do static allocation, I need to have a table with the same size for each instance of the structure. That means, I have to get the highest version value and use it as the size of the array.
Here I come : I want to create a small metaprogramming template which gives the highest version value # compile time, so I can have a fixed size of the array that will surely contains the necerrasy info for each type. But I get a compile error.
Here is a simplified sample code that reproduce the issue (the error follows it)
#include <stdio.h>
// change values here
#define VERSION_ALPHA 3
#define VERSION_BETA 5
#define VERSION_GAMMA 2
// different available types
enum TYPES
{
T_ALPHA = 0,
T_BETA,
T_GAMMA,
T_COUNT, // number of types
};
// to access versions more easily from code
static const int typeVersions[T_COUNT] =
{
VERSION_ALPHA,
VERSION_BETA,
VERSION_GAMMA
};
// this meta is used to get the highest version values between all types
template<int i>
class HighestVersion
{
private:
// version of type -1
enum
{
PREVIOUS = HighestVersion<i-1>::VALUE
};
public:
// current max value
enum
{
VALUE = (typeVersions[i] > PREVIOUS ? typeVersions[i] : PREVIOUS)
};
};
// first version
template<>
class HighestVersion<0>
{
public:
// current max value
enum
{
VALUE = typeVersions[0]
};
};
// highest version macro
#define HIGHEST_VERSION HighestVersion<T_COUNT>::VALUE
// holds info about a single type
struct TypeInfo
{
char * s_pName; // name of the type as string
unsigned int s_Flags[HIGHEST_VERSION]; // flags for each available version of this type
};
int main()
{
// instanciate
TypeInfo infos[T_COUNT];
// do stuff, set name, load flags....
/*...*/
// for test purpose, print max version value (should print 5 in this situation)
printf("%d\n", HIGHEST_VERSION);
}
The compiler says :
error C2057: expected constant expression
# the lines
VALUE = (typeVersions[i] > PREVIOUS ? typeVersions[i] : PREVIOUS)
and
VALUE = typeVersions[0]
It seems that the compiler tells me that the table's contents are not constant. I assume it's because the table is interpreted as a pointer which is no constant in that case (so if the pointer changes the contents are not the same). Is there a way to correct that so I can use the script ? It will make the user to not need to manually set the size of that table...
Thanks in advance :)
I'm sure not it's even possible to make this work with a static array.
A possible alternative is a trait class:
template<TYPES>
struct typeVersions;
// specializations for each type
template<>
struct typeVersions<T_ALPHA> { static const int version = VERSION_ALPHA; };
template<>
struct typeVersions<T_BETA> { static const int version = VERSION_BETA; };
// etc...
You'd use it like this:
enum {
VALUE = typeVersions<i>::version
};
As jrok said, it is likely not possible to do this with a static array. But neither is it
necessary to create a trait-specialization for each type-version if you have an adequately
conforming C++11 compiler.
I see that you are using VC++ and you are probably committed to it, which regrettably means the only adequately conforming C++11 compiler you might have or lay your hands right now on is VC++ 2013 Preview. If you can use that then the simple variadic template solution illustrated by the following modification of your program will work for you:
#include <stdio.h>
// change values here
#define VERSION_ALPHA 3
#define VERSION_BETA 5
#define VERSION_GAMMA 2
// different available types
enum TYPES
{
T_ALPHA = 0,
T_BETA,
T_GAMMA,
T_COUNT, // number of types
};
template<int ...Versions>
struct versions_list
{
static_assert(sizeof ...(Versions),
"Cannot have 0 versions");
};
template<int Only>
struct versions_list<Only>
{
static const int max = Only;
};
template<int First, int Last>
struct versions_list<First,Last>
{
static const int max = First > Last ? First : Last;
};
template<int First, int Second, int ...Rest>
struct versions_list<First,Second,Rest...>
{
static const int tail_max = versions_list<Second,Rest...>::max;
static const int max = First > tail_max ? First : tail_max;
};
// Update your version list here:
typedef versions_list<VERSION_ALPHA, VERSION_BETA, VERSION_GAMMA> typeVersions;
#define HIGHEST_VERSION typeVersions::max
// holds info about a single type
struct TypeInfo
{
char * s_pName; // name of the type as string
unsigned int s_Flags[HIGHEST_VERSION]; // flags for each available version of this type
};
int main()
{
// instanciate
TypeInfo infos[T_COUNT];
// do stuff, set name, load flags....
/*...*/
// for test purpose, print max version value (should print 5 in this situation)
printf("%d\n", HIGHEST_VERSION);
}
The HIGHEST_VERSION macro is really pointless here: you could just delete its definition and replace all occurrences with typeVersions::max.
By the way, if you really want to use C's stdio API in a C++ program rather than the C++
iostream API, strictly you ought to use #include <cstdio>, not #include <stdio.h>
I am not sure if this can be done with C-style arrays, but if you have compiler support for C++11 then please check my solution:
#include <array>
#include <iostream>
template <int Size, int Indice>
struct HighestValue
{
static int get(std::array<int, Size> checkedArray) {
return std::max(HighestValue<Size, Indice - 1>::get(checkedArray), checkedArray[Indice]);
}
};
template <int Size>
struct HighestValue<Size, 0>
{
static int get(std::array<int, Size> checkedArray) {
return checkedArray[0];
}
};
template<size_t Size>
int checkMax(std::array<int, Size> checkedArray)
{
return HighestValue<Size, Size - 1>::get(checkedArray);
}
int main()
{
std::array<int, 7> test {1, 5, 2, 3, 123, 5, 2};
std::cout << checkMax(test);
}
Currently I do not have spare time to play with this, but I am sure it could be improved further.

Getting type names at compile time in C++ [duplicate]

This question already has answers here:
Can I obtain C++ type names in a constexpr way?
(3 answers)
Closed 1 year ago.
I want to get the type name and print it for debug purposes. I use the following code:
#include <cxxabi.h>
inline const char* demangle(const char *s) {
abi::__cxa_demangle(s, 0, 0, NULL);
}
template<typename T>
inline const char* type_name() {
return demangle(typeid(T).name());
}
It works well, but it I suppose there is an unnecessary runtime overhead. Is there any way to get a human-readable form of type ids that is computed at compile time? I am thinking of something that looks like this:
boost::mpl::type_name<MyType>::value
Which would return a string constant of the type name.
I can't see typeid(T).name() incurring a runtime overhead. typeid(expr) yes, if expr is of a polymorphic type.
It looks like the demangling probably happens at runtime, but there's not an awful lot you can do about that. If this is only for debugging then I really wouldn't worry about it too much unless your profiler indicates that this is causing your program to slow down so much that debugging other elements of it is troublesome.
I have the same need, I've solved it using the _____FUNCTION_____ maccro in a static method of my class. But you must do some runtine computation on _____FUNCTION_____ to extract the class name. You have to do some template tricks to avoid paste the same code in every class. If someone is interessed I may clean and translate my code from french  to post it.
The main advantage of this method is that you don't need to enable RRTI. On the other hand, the extraction of the class name may be compiler dependant.
template <class MyT>
class NamedClass
{
static std::string ComputeClassName()
{
std::string funcMacro=__FUNCTION__;
//compiler dependant
static const std::string start="scul::NamedClass<class ";
static const std::string end=">::ComputeClassName";
return funcMacro.substr(start.size(),funcMacro.size()-end.size()-start.size());;
}
static const std::string _ClassName;
};
template <class MyT>
const std::string NamedClass<MyT>::_ClassName=NamedClass<MyT>::ComputeClassName();
In C++ 20
You can use the standard std::source_location where its static method ::current is consteval in which you can use it at compile-time and then you can obtain the function_name method.
template <typename T>
consteval auto func_name() {
const auto& loc = std::source_location::current();
return loc.function_name();
}
template <typename T>
consteval std::string_view type_of_impl_() {
constexpr std::string_view functionName = func_name<T>();
// since func_name_ is 'consteval auto func_name() [with T = ...]'
// we can simply get the subrange
// because the position after the equal will never change since
// the same function name is used
// another notice: these magic numbers will not work on MSVC
return {functionName.begin() + 37, functionName.end() - 1};
}
template <typename T>
constexpr auto type_of(T&& arg) {
return type_of_impl_<decltype(arg)>();
}
template <typename T>
constexpr auto type_of() {
return type_of_impl_<T>();
}
Note: The function name from the source location object may vary from compiler-to-compiler and you can use the macro __PRETTY_FUNCTION__ or any other related macros if your compiler doesn't yet support the source location library.
Usage:
int x = 4;
// type_of also preserves value category and const-qualifiers
// note: it returns std::string_view
type_of(3); // int&&
type_of(x); // int&
type_of(std::as_const(x)); // const int&
type_of(std::move(x)); // int&&
type_of(const_cast<const int&&>(x)); // const int&&
struct del { del() = delete; };
type_of<del>(); // main()::del (if inside main function)
// type_of(del{}); -- error
type_of<int>(); // int
type_of<const int&>(); // const int&
type_of<std::string_view>(); // std::basic_string_view<char>
type_of([]{}); // main()::<lambda()>&&
type_of<decltype([]{})>(); // main()::<lambda()>
type_of<std::make_index_sequence<3>>(); // std::integer_sequence<long unsigned int, 0, 1, 2>
// let's assume this class template is defined outside main function:
template <auto X> struct hello {};
type_of<hello<1>>(); // hello<1>
type_of<hello<3.14f>>(); // hello<3.1400001e+0f>
// also this:
struct point { int x, y; };
type_of<hello<point{.x = 1, .y = 2}>>() // hello<point{1, 2}>
Advantage of using this type_of over demangling in typeid(...).name():
(also noted: I didn't test other compiler's ability, so I only guarantee for GCC)
You can check the value at compile-time, such that static_assert(type_of(4.0) == "double&&") is valid.
There is no runtime overhead.
The operation can be done either at runtime or compile-time (depending on the argument given whether it's usable in a constant expression).
It preserves cv-ref traits (const, volatile, & and &&).
You can alternatively use the template argument just in case the type's constructor is deleted and test without the cv-ref traits.
You could use std::type_index to cache the demangled strings.
You could use an std::map or similar data structure (splay trees for example) to cache and access the demangled name relatively quickly. It's not done in compile time though, I doubt the latter is possible.