Template specialization for class wrappers - c++

I can't have my int class wrapper acting like a primitive int in template specialization.
I've prepared this code to explain my issue in detail:
#include <iostream>
#include <stdlib.h>
class Integer
{
int _v;
public:
constexpr explicit Integer(int v) : _v(v) {}
constexpr Integer next() const { return Integer(_v + 1); }
constexpr operator int() const { return _v;}
};
static constexpr auto integer1 = Integer(1);
static constexpr auto integer2a = Integer(2);
static constexpr auto integer2b = integer1.next();
template <const Integer& i>
void foo_Integer()
{
static auto foo_id = rand();
std::cout << foo_id << std::endl;
}
static constexpr auto int1 = 1;
static constexpr auto int2a = 2;
static constexpr auto int2b = int1 + 1;
template <int i>
void foo_int()
{
static auto foo_id = rand();
std::cout << foo_id << std::endl;
}
int main()
{
foo_int<int1>();
foo_int<int2a>();
foo_int<int2b>(); // same template specialization as above -> :)
foo_Integer<integer1>();
foo_Integer<integer2a>();
foo_Integer<integer2b>(); // different template specialization -> :(
}
As you can see running the code
foo_int<int2a>();
foo_int<int2b>();
use the same template specialization, while
foo_Integer<integer2a>();
foo_Integer<integer2b>();
use different template specializations.
This is, of course, correct, from the compiler point of view, since the template accepts a const Integer&, but I hope there are other better approaches to workaround the issue.

You can easily make Integer a structural type (C++20). Then its values are valid template parameters.
class Integer
{
public:
int _v;
constexpr explicit Integer(int v) : _v(v) {}
constexpr Integer next() const { return Integer(_v + 1); }
constexpr operator int() const { return _v;}
};
template <Integer i>
void foo_Integer()
{
static auto foo_id = rand();
std::cout << foo_id << std::endl;
}
Live
And the values would be equivalent, even if they come from objects with different identities.

Related

How to use proxy class and template to "simulate" function overload by return value?

I'll trying to dig into more c++ template and type system knowledge, I know that by overloading Type.Operator(), it will seem to "overload" by return type,
#include<iostream>
using namespace std;
class My{
int getInt() const {return 20;}
short getShort() const {return 3;}
public:
template<class T>
T get() const
template<int>
int get() const {return getInt();}
template<short>
short get() const {return getShort();}
};
struct Proxy{
My const* myOwner;
Proxy(My const* owner):myOwner(owner){}
operator int() const {return myOwner->getInt();}
operator short() const {return myOwner->getShort();}
};
int main(){
My m;
Proxy p(&m);
int _i = p;//works!
short _s = p;//works!
cout<<_i<<","<<_s<<",\n";
// How to use template My::get
int i = m.get(); // doesn't compile
short s = m.get(); // doesn't compile
cout<<i<<","<<s<<",\n";
return 0;
}
I expect there should be some way to make the following lines work to differ function call according to return type:
int i = m.get();
short s = m.get();
How to implement this? Thanks a lot.
... to "simulate" function overload by return value?
Ignoring all the boilerplate of your example and focusing solely on this question, you could define a primary template function with a deleted (primary template) definition, and add explicit (full) specializations of this function only for the types for which you want to have an "overload":
#include <cstdint>
template <typename T>
T get() = delete;
template<>
uint8_t get<uint8_t>() { return 42U; } // or delegate to getUint8().
template<>
uint32_t get<uint32_t>() { return 43U; }
template<>
float get<float>() { return 44.5F; }
int main() {
const auto a = get<uint8_t>();
const auto b = get<uint32_t>();
const auto c = get<float>();
// const auto d = get<uint16_t>(); // error: use of deleted function
}
Note however that you need to explicitly specify the type of the single type template parameter, as there is no argument of get() for template argument deduction to work with.
If I understand your goal, what's missing in your implementation (among other necessary fixes) is member function of My which actually returns a Proxy.
Something like the following (testable here)
#include<iostream>
class Proxy;
class My
{
public:
template<class T = Proxy> // <--
T get() const;
};
class Proxy
{
My const& owner_;
public:
Proxy(My const& owner) : owner_(owner)
{}
operator int() const;
operator short() const;
};
template<>
Proxy My::get<Proxy>() const {
return *this; // <---
}
template<>
int My::get<int>() const {
return 20;
}
template<>
short My::get<short>() const {
return -3;
}
Proxy::operator int() const {
return owner_.get<int>();
}
Proxy::operator short() const {
return owner_.get<short>();
}
int main()
{
My m;
int i = m.get<int>();
short s = m.get<short>();
std::cout << i << ", " << s << '\n';
int i_ = m.get();
short s_ = m.get();
std::cout << i_ << ", " << s_ << '\n';
}
You can use SFINAE like:
template <typename T,
typename std::enable_if<std::is_same<T, int>::value>::type* = nullptr>
int get() const {
return getInt();
}
template <typename T,
typename std::enable_if<std::is_same<T, short>::value>::type* = nullptr>
short get() const {
return getShort();
}
Then in Proxy class call the above functions like:
operator int() const {
return myOwner->get<int>();
}
operator short() const {
return myOwner->get<short>();
}
and in main function:
int i = m.get<int>();
short s = m.get<short>();
Demo
NOTE: The fact that the function return value is being assigned to an int or short isn't used in template parameter deduction.

Hashing types at compile-time in C++17/C++2a

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

variadic template only using type parameter

I would like to do something like this:
#include <iostream>
class a {
public:
a() : i(2) {}
template <typename ...ts>
void exec() {
f<ts...>();
std::cout << "a::()" << std::endl;
}
int i;
private:
template <typename t>
void f() {
i += t::i;
}
template <typename t, typename ...ts>
void f() {
f<t>();
f<t, ts...>();
}
};
struct b {
static const int i = -9;
};
struct c {
static const int i = 4;
};
int main()
{
a _a;
_a.exec<b,c>();
std::cout << _a.i << std::endl;
}
The idea is to get the same information from a group of classes, without the need of an object of each class.
Does anyone know if it is possible?
Thanks!
In case Your compiler does not support C++17:
template <typename ...ts>
void f() {
for ( const auto &j : { ts::i... } )
i += j;
}
In C++17, your class would simply be
class a {
public:
a() : i(2) {}
template <typename ...ts>
void exec() {
((i += ts::i), ...); // Folding expression // C++17
std::cout << "a::()" << std::endl;
}
int i;
};
Possible in C++11 too, but more verbose.
Reasons why your code is not compiling:
Syntax of specializing templates is a little different.
You need to put the most general case first.
You can't partially specialize functions, only classes.
Partial specialization is not allowed within classes, only in namespaces.
Here is an example for C++11.
#include <iostream>
template<typename t, typename ...ts>
class a {
public:
static constexpr int x = t::i + a<ts...>::x;
};
template<typename t>
class a<t> {
public:
static constexpr int x = 2 + t::i;
};
struct b {
static constexpr int i = -9;
};
struct c {
static constexpr int i = 4;
};
int main()
{
constexpr int result = a<b,c>::x;
std::cout << result << std::endl;
}
Remember that templates are calculated during compilation so, for optimization sake, it is a good idea to write them in a way that allows them to be constexpr.

Building a constexpr template struct (C++11)

I have this code, which works... so far so good :
struct _TYPEIDSTR {};
typedef _TYPEIDSTR *TYPE_ID;
template<class T> _TYPEIDSTR _TYPE_ID;
template<class T> constexpr TYPE_ID getTypeID() { return &_TYPE_ID<T>; }
calling in main like this :
constexpr TYPE_ID id1 = getTypeID<int>();
constexpr TYPE_ID id2 = getTypeID<int>();
RLOG("ID1 : " << id1);
RLOG("ID2 : " << id2);
works perfectly, and I've an unique identifier for each type used in getTypeID() call.
Now I want to build a struct that brings some info about a function :
template<typename RES, typename... ARGS> struct _GlobalOverlayInfo {
bool _member;
RES(*_fn)(ARGS...);
size_t _nargs;
TYPE_ID _argIDs;
constexpr _GlobalOverlayInfo(RES(*fn)(ARGS...)) :
_member(false),
_fn(fn),
_nargs(sizeof...(ARGS)),
_argIDs {getTypeID<ARGS>()...}
{}
};
template<typename RES, typename... ARGS>
constexpr auto getOverlayInfo(RES(*fn)(ARGS...)) {
return & _GlobalOverlayInfo<RES, ARGS...>(fn); <<---ERROR1
}
using this function :
int pippo(int x) {
return 0;
}
and calling like this :
constexpr auto x = getOverlayInfo(pippo); <<--- ERROR2
I get the 2 marked errors; ERROR1 is "taking address of a temporary" (but shouldn't it a compile time evaluation ?) and ERROR2 is "error: ‘&’ is not a constant expression".
I tried in many ways, but I couldn't success. Where I am wrong ?
Is there a way (in C++11) to achieve this result ?
All I need is a pointer to an unique structure generated for each RES and ARGS... parameters.
Not sure to understand what you want to do.
Anyway, if you want to works with C++11 (and avoid C++14 or newer) you can't use a template variable like
template<class T> _TYPEIDSTR _TYPE_ID;
that is a C++14 feature.
If you want a constexpr template identifier, you could use std::type_index, that is available starting from C++11.
If you can't use std::type_index... well, the best I can imagine is a template class with a static variable and a static method that return the pointer to it.
Something like
template <typename>
struct typeId
{
static constexpr int const val {};
static constexpr int const * getId ()
{ return &val; }
};
template <typename T>
constexpr int const typeId<T>::val;
You can get constexpr values and you can check that are differents
constexpr auto const idInt = typeId<int>::getId();
constexpr auto const idLong = typeId<long>::getId();
std::cout << (idInt != idLong) << std::endl; // print 1
For the function case...
Functions has types and single functions can be template parameters.
So if you want a constexpr identifier for a function, you can create a wrapper as follows
template <typename Ft, Ft f>
struct funcT
{ };
and, using the preceding typeId, get different constexpr values from different functions as follows
int foo (int) { return 0; }
int bar (int) { return 0; }
long baz (int, long, long long) { return 0L; }
// ...
constexpr auto idFoo = typeId<funcT<decltype(&foo), &foo>>::getId();
constexpr auto idBar = typeId<funcT<decltype(&bar), &bar>>::getId();
constexpr auto idBaz = typeId<funcT<decltype(&baz), &baz>>::getId();
std::cout << (idFoo != idBar) << std::endl; // print 1
std::cout << (idFoo != idBaz) << std::endl; // print 1

if constexpr instead of tag dispatch

I want to use if constexpr instead of tag dispatching, but I am not sure how to use it. Example code below.
template<typename T>
struct MyTag
{
static const int Supported = 0;
};
template<>
struct MyTag<std::uint64_t>
{
static const int Supported = 1;
};
template<>
struct MyTag<std::uint32_t>
{
static const int Supported = 1;
};
class MyTest
{
public:
template<typename T>
void do_something(T value)
{
// instead of doing this
bool supported = MyTag<T>::Supported;
// I want to do something like this
if constexpr (T == std::uint64_t)
supported = true;
}
};
One way is to define a constexpr predicate which checks the type of its argument, then constexpr switch on the result of that predicate.
I think this way is nice because it separates the functional logic from the precondition logic.
#include <iostream>
#include <cstddef>
#include <type_traits>
class MyTest
{
public:
template<typename T>
void do_something(T value)
{
// define our predicate
// lambdas are constexpr-if-possible in c++17
constexpr auto is_supported = [](auto&& x) {
if constexpr (std::is_same<std::decay_t<decltype(x)>, std::uint64_t>())
return true;
else
return false;
};
// use the result of the predicate
if constexpr (is_supported(value))
{
std::cout << "supported\n";
}
else
{
std::cout << "not supported\n";
}
}
};
int main()
{
auto t = MyTest();
t.do_something(int(0));
t.do_something(std::uint64_t(0));
t.do_something(double(0));
t.do_something(static_cast<unsigned long>(0)); // be careful with std::uint_xx aliases
}
example results:
not supported
supported
not supported
supported
Another way to express this might be:
class MyTest
{
public:
template<class T>
static constexpr bool something_possible(T&&)
{
return std::is_same<std::decay_t<T>, std::uint64_t>();
}
template<typename T>
void do_something(T value)
{
// switch behaviour on result of constexpr predicate
if constexpr (something_possible(value))
{
std::cout << "supported\n";
}
else
{
std::cout << "not supported\n";
}
}
};
Usually runtime interrogation of types has sense in functional programing with generic lambdas (with generic arguments too). Otherwise simple answer might be: just declare using 'required' types or use type traits, etc ... Back to the subject of generic lambdas.
/// <summary>
/// c++ 17 generic lambdas have issues
/// with required types of auto arguments
/// in c++20 this will be fixed with new
/// lambda arguments template declaration syntax
/// until then ...
/// </summary>
namespace required_types
{
template<typename RQ>
inline auto is_required_type = [](const auto & v_ = 0) constexpr -> bool
{
using T = std::decay_t< decltype(v_) >;
return std::is_same<T, RQ>();
};
inline auto is_uint64 = [] ( const auto & v_ = 0 ) constexpr -> bool
{
return is_required_type<std::uint64_t>(v_);
};
} // required_types
namespace {
using namespace required_types;
inline auto tv = [](const char prompt[] = "", const auto & value) {
std::cout << prompt << "\ntype:\t" << typeid(decltype(value)).name() << "\nvalue:\t" << value;
};
inline auto make_double_value = [](auto value)
{
if constexpr (is_uint64(value)) {
tv("\n\nDoubling required type (std::uint_64):", value);
return value + value;
}
tv("\n\nWill try to double 'illegal' type", value);
return value + value;
};
}
some usage
// call with 'legal' aka required type
std::uint64_t u42 = 42u;
auto double_value_2 = make_double_value(u42);
tv("\nResult:", double_value_2);
// call with some 'illegal' types also works
auto double_value = make_double_value(42u);
tv("\nResult:", double_value);
std::string one{"--ONE--"};
auto double_value_3 = make_double_value(one);
tv("\nResult:", double_value_3 );
Of course if one hotly disagrees with my intro one can still use my "required_types":
template<typename T>
void some_proc ( const T && val_ ) {
using namespace required_types;
if constexpr ( is_required_type<std::uint64_t>(val_) ) {
do_something_with_uint64 (val_) ;
}
}
Instead of above I would much rather use std::enable_if, somewhere along this answer.
But (as mentioned) for solving few generic lambdas issues in C++17 I would (boldly) use my namespace required_types, with some extensions.