Reputation: 146
I am looking for a construct that allows the compiler to select the minimum data type for a given integer in a template parameter. The problem why I could not find a solution on my own is how I want to use it:
template<typename T, min_type<max_elements_to_store> max_elements_to_store>
class testClass {
private:
T data[max_elements_to_store];
min_type<max_elements_to_store> currently_selected_element;
};
The macro "min_type" should dynamically choose the type with minimum number of bits for the given max_elements_to_store (uint8_t, uint16_t, uint32_t, uint64_t). I could fix it by simply replacing the min_type<> with a given data type but this data type would often not be the best possible selection. E.g.,
template<typename T, uint64_t max_elements_to_store>
class testClass {
private:
T data[max_elements_to_store];
uint64_t currently_selected_element;
};
TestClass<uint8_t, 12> testObject;
Here, the array would only hold 12 elements and the currently_selected_element variable wastes many bits that are simply not necessary for accessing only 12 elements. This seems to be only a small problem but it gets worse for many variables for accessing the data in the class...
Is there a solution for this problem? I hope it is clear what I'm looking for.
Thanks in advance! Inspire
Upvotes: 0
Views: 841
Reputation: 1935
Here is an alternative solution using constexpr functions to index into a tuple of the type options.
#include <iostream>
#include <tuple>
template <size_t size>
struct min_type {
constexpr static inline size_t log2(size_t n) {
return ( (n<2) ? 0 : 1+log2(n/2));
}
constexpr static inline size_t get_index(size_t last,size_t value) {
return value == 8 ? last : get_index(last+1,value/2);
}
constexpr static inline std::size_t min_type_size(std::size_t v) {
v--; v |= v >> 1; v |= v >> 2; v |= v >> 4;
v |= v >> 8; v |= v >> 16; v++;
return v < 8 ? 8 : v;
}
using type_options = std::tuple<uint8_t,uint16_t,uint32_t,uint64_t>;
using type = typename std::tuple_element<
get_index(0,min_type_size(log2(size))),
type_options>::type;
};
template<typename T, size_t max_elements_to_store>
class testClass {
//private:
public:
T data[max_elements_to_store];
typename min_type<max_elements_to_store>::type currently_selected_element;
};
int main()
{
min_type<0xff>::type uint_8_value;
min_type<0xffff>::type uint_16_value;
min_type<0xffffffff>::type uint_32_value;
min_type<0xffffffffffffffff>::type uint_64_value;
static_assert(std::is_same< decltype(uint_8_value),uint8_t>::value,"...");
static_assert(std::is_same< decltype(uint_16_value),uint16_t>::value,"...");
static_assert(std::is_same< decltype(uint_32_value),uint32_t>::value,"...");
static_assert(std::is_same< decltype(uint_64_value),uint64_t>::value,"...");
testClass<int,12> testcls;
static_assert(std::is_same< decltype(testcls.currently_selected_element),uint8_t>::value,"...");
return 0;
}
Upvotes: 1
Reputation: 2558
One way to do this is by using std::conditional
:
#include <type_traits>
#include <cstdint>
#include <limits>
template<std::size_t Count>
using min_type = std::conditional_t<Count <= std::numeric_limits<uint8_t>::max(), uint8_t,
std::conditional_t<Count <= std::numeric_limits<uint16_t>::max(), uint16_t,
std::conditional_t<Count <= std::numeric_limits<uint32_t>::max(), uint32_t,
std::conditional_t<Count <= std::numeric_limits<uint64_t>::max(), uint64_t, void>>>>;
// test: all of the following pass
static_assert(std::is_same_v<min_type<12>, uint8_t>);
static_assert(std::is_same_v<min_type<1024>, uint16_t>);
static_assert(std::is_same_v<min_type<70000>, uint32_t>);
static_assert(std::is_same_v<min_type<5000000000>, uint64_t>);
template<typename T, std::size_t max_elements_to_store>
class testClass {
private:
T data[max_elements_to_store];
min_type<max_elements_to_store> currently_selected_element;
};
TestClass<uint8_t, 12> testObject;
Note: as @François Andrieux pointed out in comments, this won't save any memory, due to padding (unless you use compiler-specific extension for forcing packed representation of the class).
Upvotes: 2