My Problem is the following. I want to sort a list of types based on a list of constexpr values. The problem can be boiled down to this function:
template <typename U, typename V>
auto min(U,V) -> std::conditional_t<U::value < V::value, U, V>
{ return {}; }
whereas value must be some static constexpr member of each type, respecively.
The following snippet demonstrates the usage:
// (I)
// This must even be declared outside of a function body due to the statics :(
struct X { static constexpr double value = 2.; };
struct Y { static constexpr double value = 1.; };
int main()
{
X x;
Y y;
auto z = min(x,y);
std::cout << typeid(z).name() << " : " << z.value << std::endl;
}
My goal is to provide the value as I call the function. The closest thing I got to this goal is
the following
template <double (*F)()>
struct Value { static constexpr double value = F(); };
which can be called like this using lambdas:
// (II)
auto w = min(Value<[]{ return 3.14; }>{}, Value<[]{ return 2.71; }>{});
std::cout << typeid(w).name() << " : " << w.value << std::endl;
The actual type to be sorted can be an additional parameter.
The problem is that the above is not valid C++ according to the standard. However, the latest clang does compile
this gracefully.
Now, my question is: Is there another standard compliant way to achieve the above (listing (II)), that is, defining a function that
computes a type based on constexor objects provided inplace (in some way) as the function argument?
P.S.: I'm aware of the solution using std::integral_constant. This, however, is limited to integral types only. I'm interested in a solution that works for all constexpr objects, in particular floating point types, and strings.
Edit:
To deal with floating point values as well as integral types scenarios you could make use of user defined literal template e.g.:
#include <type_traits>
#include <utility>
#include <typeinfo>
#include <iostream>
template <class FloatingPointType, class... Cs>
constexpr FloatingPointType char_list_to_(Cs... cs) {
char arr[] = {cs...};
FloatingPointType lhs = 0;
bool contains_dot = false;
for (std::size_t i = 0; i < sizeof...(Cs) && !(contains_dot |= (arr[i] == '.')); i++) {
lhs *= 10;
lhs += arr[i] - '0';
}
FloatingPointType rhs = 0;
for (int i = sizeof...(Cs) - 1; i > 0 && arr[i] != '.'; i--) {
rhs /= 10;
rhs += arr[i] - '0';
}
rhs /= 10;
return (contains_dot)?lhs+rhs:lhs;
}
template <class FloatingPointType, char... Cs>
struct FloatingPointValue {
static constexpr FloatingPointType value = char_list_to_<FloatingPointType>(Cs...);
constexpr operator FloatingPointType() {
return value;
}
};
template <class FloatingPointType, char... Cs>
constexpr FloatingPointType FloatingPointValue<FloatingPointType, Cs...>::value;
template <char... Cs>
FloatingPointValue<double, Cs...> operator""_fv() {
return {};
}
template <typename U, typename V>
auto min(U,V) -> std::conditional_t<(U{}<V{}), U, V>
{ return {}; }
int main() {
auto w = min(3.14_fv, 2.71_fv);
std::cout << typeid(w).name() << " : " << w.value << std::endl;
}
Output:
18FloatingPointValueIdJLc50ELc46ELc55ELc49EEE : 2.71
Output of c++filt -t 18FloatingPointValueIdJLc50ELc46ELc55ELc49EEE:
FloatingPointValue<double, (char)50, (char)46, (char)55, (char)49>
[live demo]
But if you wish to apply the same to string literal there is currently a lack of support of the feature caused by a c++ standard. There is however a gnu extension supported by clang and gcc if you are capable to accept less portable option:
#include <type_traits>
#include <utility>
#include <typeinfo>
#include <iostream>
template <class CharT, CharT... Cs>
struct Value {
static constexpr std::size_t size = sizeof...(Cs);
static constexpr CharT const value[sizeof...(Cs) + 1] = {Cs..., '\0'};
template <class RHS>
constexpr bool operator<(RHS) {
for (std::size_t i = 0; i < size && i < RHS::size; i++) {
if (value[i] != RHS::value[i]) {
return value[i] < RHS::value[i];
}
}
return size < RHS::size;
}
};
template <class CharT, CharT... Cs>
constexpr CharT const Value<CharT, Cs...>::value[sizeof...(Cs) + 1];
template <class CharT, CharT... Cs>
Value<CharT, Cs...> operator""_v() {
return {};
}
template <typename U, typename V>
auto min(U,V) -> std::conditional_t<(U{}<V{}), U, V>
{ return {}; }
int main() {
auto w = min("cde"_v, "abc"_v);
std::cout << typeid(w).name() << " : " << w.value << std::endl;
}
Output:
5ValueIcJLc97ELc98ELc99EEE : abc
Output of c++filt -t 5ValueIcJLc97ELc98ELc99EEE:
Value<char, (char)97, (char)98, (char)99>
[live demo]
Related
I need to define the return types (???? in code) after a sum with 2 parameters pack! My objective is to define a Tuple class (like std::tuple) and to implement for example here, the addition between two tuples of the same length and compatible type), here is a code snippet to understand :
template<class FirstType, class ... Types>
class Tuple{
....
template <typename ... OtherTypes>
const Tuple<????> operator+(Tuple<OtherTypes...>& other) const{...}
...
}
The problem is that "int + double" should return me a double for example! I looked on the std::common_type side, but I don't see either.
A tuple is constructed as follows:
private:
FirstType m_first ;
Tuple<Types...> m_next ;
m_first contains the value, and,
m_next contains the next tuple by removing a value.
This is my way of storing the different types although there is another way with a hereditary approach.
I have another class with a template for the recursive case.
So I don't see how to get the final return type, I have a get function where I used a getHelper<...> structure to return the correct type but here it's different again. I don't want to use AUTO which can cause me problems and which is not magic.
Thank you in advance for your answers.
Example : Tuple<int,double>(3,4.4) + Tuple<double,double>(3.1,3.1) = Tuple<DOUBLE,DOUBLE>(6.5,7.5)
See here for how to elementwise add two tuples https://stackoverflow.com/a/50815600/4117728. The following build on this answer. What is left to add is just to get the common type tuple:
template <typename T1,typename T2>
struct common_tuple;
template <typename...T1,typename...T2>
struct common_tuple< std::tuple<T1...>,std::tuple<T2...>> {
using type = std::tuple<std::common_type_t<T1,T2>...>;
};
Then the answer linked above can be adjusted to:
namespace internal
{
template<typename T,typename S, size_t... Is>
common_tuple<T,S>::type add_rhs_to_lhs(T& t1, const S& t2, std::integer_sequence<size_t, Is...>)
{
return std::make_tuple( (std::get<Is>(t1) + std::get<Is>(t2))... );
}
}
template <typename...T,typename...S>
std::tuple<std::common_type_t<T,S>...> operator + (std::tuple<T...> lhs, const std::tuple<S...>& rhs)
{
return internal::add_rhs_to_lhs(lhs, rhs, std::index_sequence_for<T...>{});
}
int main(int argc, char *argv[])
{
auto a = std::make_tuple(1,2,4);
auto b = std::make_tuple(1.0,2.0,4.0);
auto c = a + b;
std::cout << std::get<0>(c) << "\n";
std::cout << std::get<1>(c) << "\n";
std::cout << std::get<2>(c) << "\n";
}
However, a + b already yields the common type, hence you can simply use auto:
#include <tuple>
#include <iostream>
namespace internal
{
template<typename T,typename S, size_t... Is>
auto add_rhs_to_lhs(T& t1, const S& t2, std::integer_sequence<size_t, Is...>)
{
return std::make_tuple( (std::get<Is>(t1) + std::get<Is>(t2))... );
}
}
template <typename...T,typename...S>
auto operator + (std::tuple<T...> lhs, const std::tuple<S...>& rhs)
{
return internal::add_rhs_to_lhs(lhs, rhs, std::index_sequence_for<T...>{});
}
int main(int argc, char *argv[])
{
auto a = std::make_tuple(1,2,4);
auto b = std::make_tuple(1.0,2.0,4.0);
auto c = a + b;
std::cout << std::get<0>(c) << "\n";
std::cout << std::get<1>(c) << "\n";
std::cout << std::get<2>(c) << "\n";
}
I've encountered strange behavior when trying to construct a tuple of references from a mix of tuples and integral values.
Given the following:
struct A { int v = 1; };
struct B { int v = 2; };
struct C { int v = 3; };
A a;
std::tuple<B,C> tpl;
I'm trying to create a third tuple which holds references to all instances, so that each instance's v will be assignable and readable through it.
Seems simple enough using templates
template <class Tuple, size_t... Is>
constexpr auto as_ref_impl(Tuple t, std::index_sequence<Is...>) {
return std::tuple_cat(std::tie(std::get<Is>(t))...);
// or
// return std::make_tuple(std::ref(std::get<Is>(t))...);
}
template <class...Args>
constexpr auto as_ref(std::tuple<Args...>& t) {
return as_ref_impl(t, std::index_sequence_for<Args...>{});
}
and then
auto ref_tpl = std::tuple_cat(std::tie(a), as_ref(tpl));
which builds fine (in both versions).
Unfortunately only the parts of the reference tuple (ref_tpl), which originate from integral values, can be assigned or read from successfully.
I'm using C++14 and gcc 9.3.0.
Any ideas, or insight why this does not work, are very welcome!
Minimal working example:
#include <iostream>
#include <tuple>
#include <type_traits>
#include <utility>
#include <functional>
struct A { int v = 1; };
struct B { int v = 2; };
struct C { int v = 3; };
A a;
std::tuple<B,C> tpl;
template <class Tuple, size_t... Is>
constexpr auto as_ref_impl(Tuple t, std::index_sequence<Is...>) {
//return std::tuple_cat(std::tie(std::get<Is>(t))...);
return std::make_tuple(std::ref(std::get<Is>(t))...);
}
template <class...Args>
constexpr auto as_ref(std::tuple<Args...>& t) {
return as_ref_impl(t, std::index_sequence_for<Args...>{});
}
int main() {
using std::cout;
auto ref_tpl = std::tuple_cat(std::tie(a), as_ref(tpl));
// prints 1 2 3, as expected.
cout << a.v << std::get<0>(tpl).v << std::get<1>(tpl).v << std::endl;
std::get<0>(ref_tpl).v = 8; // works
std::get<1>(ref_tpl).v = 9; // does not work
std::get<2>(ref_tpl).v = 10; // does not work
// should output 8 9 10 instead outputs 8 2 3
cout << a.v << std::get<0>(tpl).v << std::get<1>(tpl).v << std::endl;
// should output 8 9 10, instead outputs garbage.
cout << std::get<0>(ref_tpl).v << std::get<1>(ref_tpl).v << std::get<2>(ref_tpl).v << std::endl;
return 0;
}
This is a simple typo:
constexpr auto as_ref_impl(Tuple t, std::index_sequence<Is...>) {
Tuple is taken by value, so a local copy is made, and the reference is made relative to it.
You should take Tuple by reference instead,
constexpr auto as_ref_impl(Tuple& t, std::index_sequence<Is...>) {
Your as_ref_impl needs to take the Tuple parameter by reference, otherwise you are taking a std::ref to a function local. This explains the unmodified values of tpl, and the garbage values in ref_tpl.
Do this instead:
template <class Tuple, size_t... Is>
// note the reference parameter
constexpr auto as_ref_impl(Tuple &t, std::index_sequence<Is...>) {
return std::make_tuple(std::ref(std::get<Is>(t))...);
}
Here's a demo.
Consider the following code:
#include <iostream>
#include <type_traits>
template <class T>
constexpr std::size_t type_hash(T) noexcept
{
// Compute a hash for the type
// DO SOMETHING SMART HERE
}
int main(int argc, char* argv[])
{
auto x = []{};
auto y = []{};
auto z = x;
std::cout << std::is_same_v<decltype(x), decltype(y)> << std::endl; // 0
std::cout << std::is_same_v<decltype(x), decltype(z)> << std::endl; // 1
constexpr std::size_t xhash = type_hash(x);
constexpr std::size_t yhash = type_hash(y);
constexpr std::size_t zhash = type_hash(z);
std::cout << (xhash == yhash) << std::endl; // should be 0
std::cout << (yhash == zhash) << std::endl; // should be 1
return 0;
}
I would like the type_hash function to return a hash key unique to the type, at compile-time. Is there a way to do that in C++17, or in C++2a (ideally only relying on the standard and without relying compiler intrinsics)?
I doubt that's possible with purely the standard C++.
But there is a solution that will work on most major compilers (at least GCC, Clang, and MSVC). You could hash strings returned by the following function:
template <typename T> constexpr const char *foo()
{
#ifdef _MSC_VER
return __FUNCSIG__;
#else
return __PRETTY_FUNCTION__;
#endif
}
I don't know a way to obtain a std::size_t for the hash.
But if you accept a pointer to something, maybe you can take the address of a static member in a template class.
I mean... something as follows
#include <iostream>
#include <type_traits>
template <typename>
struct type_hash
{
static constexpr int i { };
static constexpr int const * value { &i };
};
template <typename T>
static constexpr auto type_hash_v = type_hash<T>::value;
int main ()
{
auto x = []{};
auto y = []{};
auto z = x;
std::cout << std::is_same_v<decltype(x), decltype(y)> << std::endl; // 0
std::cout << std::is_same_v<decltype(x), decltype(z)> << std::endl; // 1
constexpr auto xhash = type_hash_v<decltype(x)>;
constexpr auto yhash = type_hash_v<decltype(y)>;
constexpr auto zhash = type_hash_v<decltype(z)>;
std::cout << (xhash == yhash) << std::endl; // should be 0
std::cout << (xhash == zhash) << std::endl; // should be 1
} // ...........^^^^^ xhash, not yhash
If you really want type_hash as a function, I suppose you could simply create a function that return the type_hash_v<T> of the type received.
Based on HolyBlackCat answer, a constexpr template variable which is a (naive) implementation of the hash of a type:
template <typename T>
constexpr std::size_t Hash()
{
std::size_t result{};
#ifdef _MSC_VER
#define F __FUNCSIG__
#else
#define F __PRETTY_FUNCTION__
#endif
for (const auto &c : F)
(result ^= c) <<= 1;
return result;
}
template <typename T>
constexpr std::size_t constexpr_hash = Hash<T>();
Can be used as shown below:
constexpr auto f = constexpr_hash<float>;
constexpr auto i = constexpr_hash<int>;
Check on godbolt that the values are indeed, computed at compile time.
I will agree with the other answers that it's not generally possible as-stated in standard C++ yet, but we may solve a constrained version of the problem.
Since this is all compile-time programming, we cannot have mutable state, so if you're willing to use a new variable for each state change, then something like this is possible:
hash_state1 = hash(type1)
hash_state2 = hash(type2, hash_state1)
hash_state3 = hash(type3, hash_state2)
Where "hash_state" is really just a unique typelist of all the types we've hashed so far. It can also provide a size_t value as a result of hashing a new type.
If a type that we seek to hash is already present in the typelist, we return the index of that type.
This requires quite a bit of boilerplate:
Ensuring types are unique within a typelist: I used #Deduplicator's answer here: https://stackoverflow.com/a/56259838/27678
Finding a type in a unique typelist
Using if constexpr to check if a type is in the typelist (C++17)
Live Demo
Part 1: a unique typelist:
Again, all credit to #Deduplicator's answer here on this part. The following code saves compile-time performance by doing lookups on a typelist in O(log N) time thanks to leaning on the implementation of tuple-cat.
The code is written almost frustratingly generically, but the nice part is that it allows you to work with any generic typelist (tuple, variant, something custom).
namespace detail {
template <template <class...> class TT, template <class...> class UU, class... Us>
auto pack(UU<Us...>)
-> std::tuple<TT<Us>...>;
template <template <class...> class TT, class... Ts>
auto unpack(std::tuple<TT<Ts>...>)
-> TT<Ts...>;
template <std::size_t N, class T>
using TET = std::tuple_element_t<N, T>;
template <std::size_t N, class T, std::size_t... Is>
auto remove_duplicates_pack_first(T, std::index_sequence<Is...>)
-> std::conditional_t<(... || (N > Is && std::is_same_v<TET<N, T>, TET<Is, T>>)), std::tuple<>, std::tuple<TET<N, T>>>;
template <template <class...> class TT, class... Ts, std::size_t... Is>
auto remove_duplicates(std::tuple<TT<Ts>...> t, std::index_sequence<Is...> is)
-> decltype(std::tuple_cat(remove_duplicates_pack_first<Is>(t, is)...));
template <template <class...> class TT, class... Ts>
auto remove_duplicates(TT<Ts...> t)
-> decltype(unpack<TT>(remove_duplicates<TT>(pack<TT>(t), std::make_index_sequence<sizeof...(Ts)>())));
}
template <class T>
using remove_duplicates_t = decltype(detail::remove_duplicates(std::declval<T>()));
Next, I declare my own custom typelist for using the above code. A pretty straightforward empty struct that most of you have seen before:
template<class...> struct typelist{};
Part 2: our "hash_state"
"hash_state", which I'm calling hash_token:
template<size_t N, class...Ts>
struct hash_token
{
template<size_t M, class... Us>
constexpr bool operator ==(const hash_token<M, Us...>&)const{return N == M;}
constexpr size_t value() const{return N;}
};
Simply encapsulates a size_t for the hash value (which you can also access via the value() function) and a comparator to check if two hash_tokens are identical (because you can have two different type lists but the same hash value. e.g., if you hash int to get a token and then compare that token to one where you've hashed (int, float, char, int)).
Part 3: type_hash function
Finally our type_hash function:
template<class T, size_t N, class... Ts>
constexpr auto type_hash(T, hash_token<N, Ts...>) noexcept
{
if constexpr(std::is_same_v<remove_duplicates_t<typelist<Ts..., T>>, typelist<Ts...>>)
{
return hash_token<detail::index_of<T, Ts...>(), Ts...>{};
}
else
{
return hash_token<N+1, Ts..., T>{};
}
}
template<class T>
constexpr auto type_hash(T) noexcept
{
return hash_token<0, T>{};
}
The first overload is for the generic case; you've already "hashed" a number of types, and you want to hash yet another one. It checks to see if the type you're hashing has already been hashed, and if so, it returns the index of the type in the unique type list.
To accomplish getting the index of a type in a typelist, I used simple template expansion to save some compile time template instantiations (avoiding a recursive lookup):
// find the first index of T in Ts (assuming T is in Ts)
template<class T, class... Ts>
constexpr size_t index_of()
{
size_t index = 0;
size_t toReturn = 0;
using swallow = size_t[];
(void)swallow{0, (void(std::is_same_v<T, Ts> ? toReturn = index : index), ++index)...};
return toReturn;
}
The second overload of type_hash is for creating an initial hash_token starting at 0.
Usage:
int main()
{
auto x = []{};
auto y = []{};
auto z = x;
std::cout << std::is_same_v<decltype(x), decltype(y)> << std::endl; // 0
std::cout << std::is_same_v<decltype(x), decltype(z)> << std::endl; // 1
constexpr auto xtoken = type_hash(x);
constexpr auto xytoken = type_hash(y, xtoken);
constexpr auto xyztoken = type_hash(z, xytoken);
std::cout << (xtoken == xytoken) << std::endl; // 0
std::cout << (xtoken == xyztoken) << std::endl; // 1
}
Conclusion:
Not really useful in a lot of code, but this may help solve some constrained meta-programming problems.
I don't think it is possible. "hash key unique to the type" sounds like you are looking for a perfect hash (no collisions). Even if we ignore that size_t has a finite number of possible values, in general we can't know all the types because of things like shared libraries.
Do you need it to persist between runs? If not, you can set up a registration scheme.
I would like to improve upon #max66's answer (which is absolutely brilliant and simple by the way, I wish I thought of it)
<TL;DR>
Put inline on the static variables in max66's answer and it will make sure the same static value is present across all translation units.
</TL;DR>
If you have multiple translation units, then, typically, multiple instances of a static variable (in max66's case i) will be created, changing the value of the hash, this could be a problem, say I have a function getHash that I want to use to check if two types are the same
#include <iostream>
#include <type_traits>
#include <memory>
// <From max66's answer>
template <typename>
struct type_hash
{
static constexpr int i { };
static constexpr int const * value { &i };
};
template <typename T>
static constexpr auto type_hash_v = type_hash<T>::value;
// </From max66's answer>
struct AnyBase {
using PointerType = std::unique_ptr<AnyBase>;
constexpr virtual bool equal(const PointerType& other) const noexcept = 0;
constexpr virtual const int* typeHash() const noexcept = 0;
};
template<typename ParameterType>
struct Any : public AnyBase
{
using BasePointerType = std::unique_ptr<AnyBase>;
using Type = ParameterType;
Type value;
constexpr Any(Type value) noexcept : value(value) {}
constexpr virtual bool equal(const BasePointerType& other) const noexcept final
{
if(other->typeHash() == typeHash()) {
const auto unwrapped = dynamic_cast<const Any<Type>*>(other.get());
return unwrapped->value == value;
}
return false;
}
constexpr virtual const int* typeHash() const noexcept final {
return type_hash_v<Type>;
}
};
using AnyType = std::unique_ptr<AnyBase>;
template<typename ParameterType>
AnyType makeAny(auto... initializationValues) {
return static_cast<AnyType>(std::make_unique<Any<ParameterType>>(initializationValues...));
}
int main()
{
AnyType any0 = makeAny<int>(4);
AnyType any1 = makeAny<int>(2);
AnyType any2 = makeAny<int>(4);
AnyType any3 = makeAny<char>('A');
std::cout << "Hash Codes: "
<< any0->typeHash() << "\n"
<< any1->typeHash() << "\n"
<< any2->typeHash() << "\n"
<< any3->typeHash() << "\n";
std::cout
<< "any0 == any1? " << any0->equal(any1) << "\n" // False within translation unit
<< "any0 == any2? " << any0->equal(any2) << "\n" // True within translation unit
<< "any0 == any3? " << any0->equal(any3) << "\n"; // False within translation unit
return 0;
}
If I instantiate two Any<int>'s in two different translation units, they may have two different hashes, because i is static and is likely to have a different addresses across the translation units, therefore when I try to compare Any<int>'s it will fail even if they have the same type and value.
I learned from Daisy Hollman's presentation at CppNow (slides here) that the C++ standard guarantees a single persistent instantiation of an object across translation units
Unfortunately the way she does type registration at the beginning of the presentation is not constexprable because it relies on a static mutable variable within a function. However using this knowledge we can tweak max66's approach, lets modify the code from before
#include <iostream>
#include <type_traits>
#include <memory>
#include <bit>
template<typename ParameterType>
struct Registrar
{
constexpr inline static const uintptr_t getHash() { // ACCORDING TOO C++ STANDARD INLINE GUARANTEES ONE COPY ACROSS ALL TRANSLATION UNITS
return std::bit_cast<uintptr_t>(&hashObject);
}
protected:
constinit inline static const size_t hashObject = 0; // ACCORDING TOO C++ STANDARD INLINE GUARANTEES ONE COPY ACROSS ALL TRANSLATION UNITS
};
struct AnyBase {
using PointerType = std::unique_ptr<AnyBase>;
constexpr virtual bool equal(const PointerType& other) const noexcept = 0;
constexpr virtual const uintptr_t typeHash() const noexcept = 0;
};
template<typename ParameterType>
struct Any : public AnyBase
{
using BasePointerType = std::unique_ptr<AnyBase>;
using Type = ParameterType;
Type value;
constexpr Any(Type value) noexcept : value(value) {}
constexpr virtual bool equal(const BasePointerType& other) const noexcept final
{
if(other->typeHash() == typeHash()) {
const auto unwrapped = dynamic_cast<const Any<Type>*>(other.get());
return unwrapped->value == value;
}
return false;
}
constexpr virtual const uintptr_t typeHash() const noexcept final {
return Registrar<Type>::getHash();
}
};
using AnyType = std::unique_ptr<AnyBase>;
template<typename ParameterType>
AnyType makeAny(auto... initializationValues) {
return static_cast<AnyType>(std::make_unique<Any<ParameterType>>(initializationValues...));
}
int main()
{
AnyType any0 = makeAny<int>(4);
AnyType any1 = makeAny<int>(2);
AnyType any2 = makeAny<int>(4);
AnyType any3 = makeAny<char>('A');
std::cout << "Hash Codes: "
<< any0->typeHash() << "\n"
<< any1->typeHash() << "\n"
<< any2->typeHash() << "\n"
<< any3->typeHash() << "\n";
std::cout
<< "any0 == any1? " << any0->equal(any1) << "\n" // False GUARANTEED across translation units
<< "any0 == any2? " << any0->equal(any2) << "\n" // True GUARANTEED across translation units
<< "any0 == any3? " << any0->equal(any3) << "\n"; // False GUARANTEED across translation units
return 0;
}
Now our hash is guaranteed across translation units (as I stated in all caps :) )
Thanks to #max66 and Daisy Hollman
And a note, I think you could further static_cast to size_t or something if you want from uintptr_t, both examples compile with gcc 12.2 with -std=c++23
Suppose we have code like this. It works well and pre-calculate first 5 Fibonacci numbers.
#include <iostream>
template <int T>
struct fib;
template <>
struct fib<0>{
constexpr static int value = 1;
};
template <>
struct fib<1>{
constexpr static int value = 1;
};
template <int I>
struct fib{
constexpr static int value = fib<I - 1>::value + fib<I - 2>::value;
};
int main(){
std::cout << fib<0>::value << std::endl;
std::cout << fib<1>::value << std::endl;
std::cout << fib<2>::value << std::endl;
std::cout << fib<3>::value << std::endl;
std::cout << fib<4>::value << std::endl;
std::cout << fib<5>::value << std::endl;
}
However there is "small" problem with it.
What if we need to use this for values, that are not known at compile time?
For few values we can do this:
const int max = 5;
int getData(){
return 5; // return value between 0 and max.
}
int something(){
switch(getData()){
case 0: return fib<0>::value;
case 1: return fib<1>::value;
case 2: return fib<2>::value;
case 3: return fib<3>::value;
case 4: return fib<4>::value;
case 5: return fib<5>::value;
}
}
This will works OK for 5 values, but what if we have 150 or 300?
Is not really serious to change the code with 300 rows...
What could be the workaround here?
If you need to use a value at runtime that isn't known at compile time, you can't compute it at compile time. Obvious.
But... if you can impose a top value to values needed, you can compute all values (from zero to top) at compile time and store them in an std::array.
In the following example I have modified your fib structs (to use a std::size_t index and a template type (with default unsigned long) for the value) and I have added a templated struct fibVals that contain an std::array that is initialized using fib<n>::value
The following main() show that is possible to define a constexpr fibvals<N> (with N == 20 in the example) to compute (at compile time) all fib<n> values in range [0,N[.
#include <array>
#include <utility>
#include <iostream>
template <std::size_t, typename T = unsigned long>
struct fib;
template <typename T>
struct fib<0U, T>
{ constexpr static T value { T(1) }; };
template <typename T>
struct fib<1U, T>
{ constexpr static T value { T(1) }; };
template <std::size_t I, typename T>
struct fib
{ constexpr static T value { fib<I-1U>::value + fib<I-2U>::value }; };
template <std::size_t I, typename T = unsigned long>
struct fibVals
{
const std::array<T, I> vals;
template <std::size_t ... Is>
constexpr fibVals ( std::index_sequence<Is...> const & )
: vals { { fib<Is, T>::value ... } }
{ }
constexpr fibVals () : fibVals { std::make_index_sequence<I> { } }
{ }
};
int main()
{
constexpr fibVals<20> fv;
for ( auto ui = 0U ; ui < fv.vals.size() ; ++ui )
std::cout << "fib(" << ui << ") = " << fv.vals[ui] << std::endl;
}
Unfortunately this example use std::make_index_sequence<I> and std::index_sequence<Is...> that are C++14 features.
If you want implement struct fibVals in C++11, you can implement the following structs struct indexSeq and struct indexSeqHelper, to substitute std::index_sequence<Is...> and std::make_index_sequence<I>
template <std::size_t ...>
struct indexSeq
{ };
template <std::size_t N, std::size_t ... Next>
struct indexSeqHelper
{ using type = typename indexSeqHelper<N-1U, N-1U, Next ... >::type; };
template <std::size_t ... Next >
struct indexSeqHelper<0U, Next ... >
{ using type = indexSeq<Next ... >; };
and implement fibVals constructors as follows
template <std::size_t ... Is>
constexpr fibVals ( indexSeq<Is...> const & )
: vals { { fib<Is, T>::value ... } }
{ }
constexpr fibVals () : fibVals { typename indexSeqHelper<I>::type { } }
{ }
Templates are evaluated at compile time, so there is no solution with templates that works at runtime.
You can make a constexpr function, which may be evaluated at compile time, depending on the value passed. Obviously, a runtime value may not be computed at compile time, as it is not known at compile time.
Say I have
constexpr const std::uint8_t major = 1;
constexpr const std::uint8_t minor = 10;
constexpr const std::uint8_t bugfix = 0;
and I want
constexpr const char* version_string(){ ... }
to return the equivalent of "1.10.0" in this example, how would I do it?
I assume I'll need both of these, in constexpr:
integer to string conversion
string concatenation
The problem is purely academic, and I see little to no use to actually have it constexpr other than "it's possible". I just can't see how this would pan out. I'm willing to accept C++1y solutions that work on GCC 4.9 and Clang 3.4/3.5.
I believe I have found nearly what I seek on some Japanese blogs:
constexpr itoa
constexpr strcat
I will see what I can do with these, and perhaps answer this self-declared interesting question myself when I'm satisfied with the result.
Here's a little C++1y solution --- I think I LOVE C++1y.
#include <utility>
template<int N>
struct c_string
{
int length;
char str[N+1];
constexpr explicit c_string(int p_length)
: length(p_length), str{}
{}
};
template<int M>
constexpr auto make_c_string(char const (&str)[M])
{
c_string<M-1> ret{M-1};
for(int i = 0; i < M; ++i)
{
ret.str[i] = str[i];
}
return ret;
}
template<int N, int M>
constexpr auto join(c_string<N> const& x, c_string<M> const& y)
{
c_string<N+M> ret{x.length + y.length};
for(int i = 0; i < x.length; ++i)
{
ret.str[i] = x.str[i];
}
for(int i = 0; i < y.length; ++i)
{
ret.str[i+x.length] = y.str[i];
}
ret.str[N+M] = '\0';
return ret;
}
template<int N, int M>
constexpr auto operator+(c_string<N> const& x, c_string<M> const& y)
{
return join(x, y);
}
template<class T>
constexpr void c_swap(T& x, T& y)
{
T tmp( std::move(x) );
x = std::move(y);
y = std::move(tmp);
}
// from http://en.cppreference.com/w/cpp/algorithm/reverse
template<class I>
constexpr void reverse(I beg, I end)
{
while(beg != end && beg != --end)
{
c_swap(*beg, *end);
++beg;
}
}
Now the constexpr itoa:
#include <limits>
template<class T>
constexpr auto c_abs(T x)
{
return x < T{0} ? -x : x;
}
template<class T>
constexpr auto ntoa(T n)
{
c_string< std::numeric_limits<T>::digits10 + 1 > ret{0};
int pos = 0;
T cn = n;
do
{
ret.str[pos] = '0' + c_abs(cn % 10);
++pos;
cn /= 10;
}while(cn != T{0});
if(n < T{0})
{
ret.str[pos] = '-';
++pos;
}
ret.str[pos] = '\0';
ret.length = pos;
reverse(ret.str, ret.str+ret.length);
return ret;
}
We can then simplify the usage:
#include <type_traits>
// not supported by the libstdc++ at coliru
//template<class T, class = std::enable_if_t< std::is_arithmetic<T>{} >>
template<class T, class = typename std::enable_if<std::is_arithmetic<T>{}>::type>
constexpr auto to_c_string(T p)
{
return ntoa(p);
}
template<int N>
constexpr auto to_c_string(char const (&str)[N])
{
return make_c_string(str);
}
template<class T, class U, class... TT>
constexpr auto to_c_string(T&& p0, U&& p1, TT&&... params)
{
return to_c_string(std::forward<T>(p0))
+ to_c_string(std::forward<U>(p1), std::forward<TT>(params)...);
}
And a usage example:
#include <iostream>
int main()
{
constexpr auto res = to_c_string(42," is the solution, or is it ",-21,"?");
std::cout << res.str;
}
Live example # coliru's clang++3.4
Here is a C++11 solution. It uses class templates with char... parameter pack to simulate strings:
#include <iostream>
#include <type_traits>
template <char... symbols>
struct String
{
static constexpr char value[] = {symbols...};
};
template <char... symbols>
constexpr char String<symbols...>::value[];
template <typename, typename>
struct Concat;
template <char... symbols1, char... symbols2>
struct Concat<String<symbols1...>, String<symbols2...>>
{
using type = String<symbols1..., symbols2...>;
};
template <typename...>
struct Concatenate;
template <typename S, typename... Strings>
struct Concatenate<S, Strings...>
{
using type = typename Concat<S, typename Concatenate<Strings...>::type>::type;
};
template <>
struct Concatenate<>
{
using type = String<>;
};
template <std::size_t N>
struct NumberToString
{
using type = typename Concat
<
typename std::conditional<(N >= 10), typename NumberToString<N / 10>::type, String<>>::type,
String<'0' + N % 10>
>::type;
};
template <>
struct NumberToString<0>
{
using type = String<'0'>;
};
constexpr const std::uint8_t major = 1;
constexpr const std::uint8_t minor = 10;
constexpr const std::uint8_t bugfix = 0;
using VersionString = Concatenate
<
NumberToString<major>::type,
String<'.'>,
NumberToString<minor>::type,
String<'.'>,
NumberToString<bugfix>::type
>::type;
constexpr const char* version_string = VersionString::value;
int main()
{
std::cout << version_string << std::endl;
}
See live example.
Here is my quick and dirty solution: http://coliru.stacked-crooked.com/a/43c9b365f6435991
It exploits the fact that a 'major.minor.fix' string will be quite short, so I just use a fixed-size array big enough to hold the string. The int-to-string function uses push_front to prepend characters to string, so the whole string grows from end of buffer.
#include <cstdint>
constexpr const std::uint8_t major = 1;
constexpr const std::uint8_t minor = 10;
constexpr const std::uint8_t bugfix = 0;
struct char_array {
constexpr char_array() : ofs{sizeof(value) - 1}, value{} {}
constexpr const char* c_str() const { return value + ofs; }
constexpr void push_front(char c) {
--ofs;
value[ofs] = c;
}
private:
int ofs;
char value[42]; // big enough to hold version string
};
constexpr char_array predend_int(char_array a, int x) {
do {
auto digit = x % 10;
x = x / 10;
a.push_front(digit + '0');
}
while (x);
return a;
}
constexpr auto version_string() {
char_array a;
a = predend_int(a, bugfix);
a.push_front('.');
a = predend_int(a, minor);
a.push_front('.');
a = predend_int(a, major);
return a;
}
#include <iostream>
int main() {
constexpr char_array ver = version_string();
std::cout << ver.c_str() << '\n';
}
Update:
It's probably better to make a dedicated class for version string generation, and put all the functions in it: http://coliru.stacked-crooked.com/a/5e5ee49121cf6205