How to implement a template provding different values for different tyes - c++

I am using a library which define various constants, like JQX_TYPE_INT, JQX_TYPE_LONG, JQX_TYPE_DOUBLE etc.
My problem is to relate values to c++ types.
For example, in order to have a function which works for various types i have to implement a copy of the same code, differing only in the constant, for every c++-type, e.g.:
myObj *createObject(int iSize, int iDummy) {
int iRealSize = calcEffectiveSize(iSize, JQX_TYPE_INT);
return new myObj(iRealSize);
}
myObj *createObject(int iSize, double dDummy) {
int iRealSize = calcEffectiveSize(iSize, JQX_TYPE_DOUBLE);
return new myObj(iRealSize);
}
// ... same for float char, etc.
That is, apart from having to implement the same code several times, i have to use a dummy variable in order to use the constant related to the c++-type.
Is there some sort of template approach to do something like
template<tyepename T>
struct jqx_type {
static const int type;
// ... template magic
}
template<<typename T>
myObj *createObject(int iSize) {
int iRealSize = calcEffectiveSize(iSize, jqx_type<T>::type);
return new myObj(iRealSize);
}
I tried this
template<typename T>
struct jqx_type {
static const int type = JQX_TYPE_NONE;
};
template<typename T=int>
struct jqx_type {
static const int type = JQX_TYPE_INT;
};
template<typename T=long>
class jqx_types {
static const int type = JQX_TYPE_LONG;
};
But this doesn't even compile.
Is there a way to achieve what i intend to do?

Here:
template<typename T>
struct jqx_type {
static const int type = JQX_TYPE_NONE;
};
template<typename T=int>
struct jqx_type {
static const int type = JQX_TYPE_INT;
};
template<typename T=long>
class jqx_types {
static const int type = JQX_TYPE_LONG;
};
You redeclared the same template several times with different default arguments. What you actually want is one template with different specializations:
template<typename T>
struct jqx_type {
static const int type = JQX_TYPE_NONE;
};
template<>
struct jqx_type<int> {
static const int type = JQX_TYPE_INT;
};
template<>
struct jqx_types<long> {
static const int type = JQX_TYPE_LONG;
};
PS: type is very common name for member type aliases. I would not use it for an int. Maybe type_selector is a better name.

I would go with a much simpler
myObj *createObject(int iSize, int jqx_type) {
return new myObj(calcEffectiveSize(iSize, jqx_type));
}
myObj *o = createObject(16, JQX_TYPE_DOUBLE);
or if it needs to be constexpr
template <int jqx_type>
myObj *createObject(int iSize) {
return new myObj(calcEffectiveSize(iSize, jqx_type));
}
myObj *o = createObject<JQX_TYPE_DOUBLE>(16);
but lets assume you need the actual type it could be something like this:
template <typename T>
struct MyObj {
MyObj(std::size_t size, int jqx_type)
: iSize{calcEffectiveSize(size, jqx_type)} { ... }
MyObj * create(std::size_t size) {
constexpr int jqx_type = []() {
if constexpr (std::is_same_v<T, int>) return JQX_TYPE_INT;
if constexpr (std::is_same_v<T, double>) return JQX_TYPE_DOUBLE;
}();
return new MyObj{iSize, jqx_type};
}
};
or if you do want to go the type traits route then you have to specialize the template struct like this:
template<typename T>
struct jqx_type {
static const int type = JQX_TYPE_NONE;
};
template<>
struct jqx_type<int> {
static const int type = JQX_TYPE_INT;
};
template<>
struct jqx_types<long> {
static const int type = JQX_TYPE_LONG;
};
But that's as much code as specializing the createObject function.
PS: you can match and mix ideas, too. E.g. template createObject function with the if constexpr lambda.

Related

How to instantiate a list of types for compile-time/static polymorphism

I'm implementing a compile time dispatcher which makes use of static polymorphism and metaprogramming.
I have a list of types which I would like to instantiate into a runtime std::array.
struct Test
{
typedef std::integral_constant<int,0> nop;
typedef std::integral_constant<int,1> A;
typedef std::integral_constant<int,2> B;
typedef std::integral_constant<int,3> C;
using list = mp_list<A, B, C>; // mp_list expands to: template<A, B, C> struct {};
struct Things{
int (Test::*process)(int foo, float bar);
const std::string key;
int something;
float other;
};
typedef std::array<Things, mp_size<list>::value> Thing_list;
Thing_list thing_list;
template<typename T=nop> int process(int foo, float bar);
// stuff...
Test();
}
In the above code, mp_list is simply a variadic template which 'expands' to struct<A, B, C> mp_list. And likewise, mp_size gives an mp implementation of the sizeof.
As can be inferred, the Thing_list is an array with a compile-time known size.
I can then specialize a template function like so:
template<> int process<Test::B>(int foo, float bar){ /* do stuff */ };
to achieve compile-time polymorphism.
The above code works well, except that to initialize it, I am stuck at doing this in the constructor:
Test::Test() thing_list({{{&Test::process<A>, "A"}, // this all should be achieved through meta-programming
{&Test::process<B>, "B"},
{&Test::process<C>, "C"}}}} )
{
// stuff
}
There are two things I'm unable to get right:
I would like to have a MP based initialization of the list. As I update the list definition in the declaration, I would like my initialization to automatically reflect that list type.
I would also like to avoid having to duplicate the name of the type as a string literal. I have tried to use something like integral_constant but the use of const char* as a template parameter seems to be forbidden. I am left to having to duplicate declaration (triplicate, really).
This answer is almost the solution: it takes a list of types and instantiates them like so:
static std::tuple<int*, float*, foo*, bar*> CreateList() {
return { Create<int>(), Create<float>(), Create<foo>(), Create<bar>() };
}
However, I'm stuck on converting from std::tuple to std::array.
The main question is #1. Bonus for #2 without using #define based trickery.
If anyone cares: this code is destined for embedded software. There are dozens of different types, and importantly, each type (e.g. A, B, C) will have an identically structured configuration to be loaded from memory (e.g. under a configuration key for "A") - hence the reason of wanting to have access to the string name of the type at runtime.
Not sure to understand what do you exactly want but...
Given that you can use at least C++17 (for auto template parameters), you can define outside your class some variables as
static constexpr char nops[] = "NOP";
static constexpr char A[] = "A";
static constexpr char B[] = "B";
static constexpr char C[] = "C";
Then a simple wrapper that accept nops, A, B, etc. as template parameters
template <auto val>
struct wrap
{ };
Then a using that, given a variadic list of template value parameters, create a mp_list of wrap types
template <auto ... vals>
using wrapper = mp_list<wrap<vals>...>;
At this point... I suppose that, inside Test, you can define nop and list as follows
using nop = wrap<nops>;
using list = wrapper<A, B, C>;
Using delegating constructor, meta-programming way to initialize your thing_list could be the following
template <auto ... vals>
Test (mp_list<wrap<vals>...>)
: thing_list{{{&Test::process<wrap<vals>>, vals}...}}
{ }
Test () : Test{list{}}
{ }
If you modify the list adding a D parameter (where D is the "D" literal)
using list = wrapper<A, B, C, D>;
automagically the you get an additional {&Test::process<wrap<D>>, D} element in your thing_list.
The following is a full compiling C++17 example
#include <array>
#include <string>
#include <type_traits>
template <typename...>
struct mp_list
{ };
template <typename>
struct mp_size;
template <typename ... Ts>
struct mp_size<mp_list<Ts...>>
: public std::integral_constant<std::size_t, sizeof...(Ts)>
{ };
static constexpr char nops[] = "NOP";
static constexpr char A[] = "A";
static constexpr char B[] = "B";
static constexpr char C[] = "C";
template <auto val>
struct wrap
{ };
template <auto ... vals>
using wrapper = mp_list<wrap<vals>...>;
struct Test
{
using nop = wrap<nops>;
using list = wrapper<A, B, C>;
struct Things
{
int (Test::*process)(int foo, float bar);
const std::string key;
// int something;
// float other;
};
using Thing_list = std::array<Things, mp_size<list>::value>;
Thing_list thing_list;
template<typename T=nop> int process(int foo, float bar)
{ return 0; }
template <auto ... vals>
Test (mp_list<wrap<vals>...>)
: thing_list{{{&Test::process<wrap<vals>>, vals}...}}
{ }
Test () : Test{list{}}
{ }
};
int main ()
{
Test t;
}
I would suggest changing the typedefs for A, B and C to struct so you can define the string inside them.
struct A {
static constexpr int value = 1;
static constexpr char name[] = "A";
};
// Same for B and C
using list = mp_list<A, B, C>;
Then you can create a make_thing_list
template <typename... T>
static std::array<Things, sizeof...(T)> make_thing_list(mp_list<T...>) {
return {{{&Test::process<T>, T::name}...}};
}
auto thing_list = make_thing_list(list{});
Complete example
#include <string>
#include <array>
#include <iostream>
template <typename... T>
struct mp_list {};
struct Test
{
struct nop {
static constexpr int value = 0;
static constexpr char name[] = "nop";
};
struct A {
static constexpr int value = 1;
static constexpr char name[] = "A";
};
struct B {
static constexpr int value = 2;
static constexpr char name[] = "B";
};
struct C {
static constexpr int value = 3;
static constexpr char name[] = "C";
};
using list = mp_list<A, B, C>; // mp_list expands to: template<A, B, C> struct {};
struct Things{
int (Test::*process)(int foo, float bar);
const std::string key;
int something;
float other;
};
template <typename... T>
static std::array<Things, sizeof...(T)> make_thing_list(mp_list<T...>) {
return {{{&Test::process<T>, T::name}...}};
}
using Thing_list = decltype(make_thing_list(list{}));
Thing_list thing_list = make_thing_list(list{});
template<typename T=nop> int process(int foo, float bar) {
return T::value;
}
// stuff...
Test() {}
};
int main() {
Test t;
static_assert(std::is_same_v<decltype(t.thing_list), std::array<Test::Things, 3>>);
for (auto& thing : t.thing_list) {
std::cout << thing.key << (t.*thing.process)(1, 1.0) << '\n';
}
}

static function in template struct not using default template argument

I have this code which I wasn't able to compile and I was wondering if there's a way around it. Error is - Argument list for class template "a" is missing.
//not compiling one
template <typename T = int>
struct a {
static T x;
static T function(T number) {
return x = number;
}
};
template <typename T>
T a<T>::x;
int main() {
int b = a::function(5);
return 0;
}
.
//compiling one
template <typename T = int>
struct a {
static T x;
static T function(T number) {
return x = number;
}
};
template <typename T>
T a<T>::x;
int main() {
int b = a<int>::function(5);
return 0;
}
Why can it not use the template argument we passed on default and how can we fix that without entering the template parameter?
The default template parameter int can be used without specifying it, you just need to specify that a is a template:
int b = a<>::function(5);
// ^^
is there a way to do without a<>, just a?
In case your class template a is only intended to provide utility static functions and not act as an object (with state), you could use delegation via a function template, which returns a (dummy) a object, followed by using the fact that an object of a given type, say A, can invoke non-static as well as static member functions.
namespace detail {
template <typename T = int>
struct AImpl {
static T x;
static T function(T number) {
return x = number;
}
};
template <typename T>
T AImpl<T>::x;
} // namespace detail
template<typename T = int>
constexpr detail::AImpl<T> a() { return {}; }
int main() {
const auto b_int = a().function(5);
const auto b_char = a<char>().function('a');
(void)b_int; (void)b_char;
}
If you in fact always wants to use deduction and never actually specify the type of the type template parameter (when other than int), you could exchange class template and its static data member and member function by a single function template that wraps a variable with static storage duration:
#include <type_traits>
template<typename T>
T function(T number) {
static T x;
return x = number;
}
int main() {
const auto b_int = function(5);
const auto b_char = function('a');
static_assert(std::is_same_v<decltype(b_int), const int>, "");
static_assert(std::is_same_v<decltype(b_char), const char>, "");
(void)b_int; (void)b_char;
}
This would be an entirely different (and more implicit) API, however.

std::initializer_list value as template parameter

I am trying to do something like this:
template<typename enumType,
std::initializer_list<enumType> values,
std::initializer_list<std::string> mappings>
struct enum_converter {
enumType toEnum(const std::string& literal) { ... }
std::string toString(const enumType value) { ... }
};
I want to use it as follows:
enum test_enum {value_a, value_b};
struct test_enum_converter : public enum_converter<
test_enum,
{value_a, value_b},
{"a", "b"}> {};
GCC tells me:
class std::initializer_list<_Tp> is not a valid type
for a template constant parameter.
Adding const to the type does not change anything. Is there a workaround or a similar solution?
Only integral types, enumerations, pointers and references are allowed as non-type template parameters. std::initializer_list is neither of those.
Do you need the values & mappings to be template parameters? How about making them normal parameters of the constructor, and keeping only the enum as a template param?
template<typename enumType>
struct enum_converter {
enum_converter(std::initializer_list<enumType> values, std::initializer_list<std::string> mappings) : values(values), mappings(mappings)
enumType toEnum(const std::string& literal) { ... }
std::string toString(const enumType value) { ... }
private:
std::initializer_list<enumType> values;
std::initializer_list<std::string> mappings;
};
enum_converter<test_enum> test_enum_converter({...}, {...});
int main()
{
test_enum_converter.toEnum("bla");
}
EDIT
Here is an instance-free alternative:
template<typename enumType>
struct enum_converter {
static init(std::initializer_list<enumType> values, std::initializer_list<std::string> mappings)
{ s_values = values; s_mappings = mappings; }
static enumType toEnum(const std::string& literal) { ... }
static std::string toString(const enumType value) { ... }
private:
static std::initializer_list<enumType> s_values;
static std::initializer_list<std::string> s_mappings;
};
Just call init() once before using the class.
If you need more than one instantiation for a particular enumeration, you can add a disambiguation parameter to the template, like this:
template <typename enumType, typename tag>
struct enum_converter { /*as before*/ };
int main()
{
struct data_strings;
struct user_readable_strings;
enum_converter<test_enum, data_strings>::init({...}, {"a", "b"});
enum_converter<test_enum, user_readable_strings>::init({...}, {"Option A", "Option B"});
}
Pointers can only be template arguments if they are constant expressions, i.e. the address of an object with extern linkage. The following code works, though it's your call whether it's worth it:
typedef char const * charptype;
template <int, charptype> struct item;
template <typename ...> struct econv
{
static constexpr charptype convert(int) { return nullptr; }
};
template <int I, charptype S, typename ...Tail>
struct econv<item<I, S>, Tail...>
{
static constexpr charptype convert(int i)
{
return i == I ? S : econv<Tail...>::convert(i);
}
};
#include <iostream>
extern char const Hello[] = "Hello";
extern char const World[] = "World";
int main()
{
std::cout << econv< item<1, Hello>
, item<2, World> >::convert(2)
<< "\n";
}
By putting the strings into an anonymous namespace and using some helper macros, this approach might be cleaned up a bit to look presentable.

Ambiguous template arguments not excluded by enable_if

I want to automatically choose the right pointer-to-member among overloaded ones based on the "type" of the member, by removing specializations that accept unconcerned members (via enable_if).
I have the following code:
class test;
enum Type
{
INT_1,
FLOAT_1,
UINT_1,
CHAR_1,
BOOL_1,
INT_2,
FLOAT_2,
UINT_2,
CHAR_2,
BOOL_2
};
template<typename T, Type Et, typename func> struct SetterOk { static const bool value = false; };
template<typename T> struct SetterOk<T,INT_1,void (T::*)(int)> { static const bool value = true; };
template<typename T> struct SetterOk<T,FLOAT_1,void (T::*)(float)> { static const bool value = true; };
template<typename T> struct SetterOk<T,UINT_1,void (T::*)(unsigned int)> { static const bool value = true; };
template<typename T> struct SetterOk<T,CHAR_1,void (T::*)(char)> { static const bool value = true; };
template<typename T> struct SetterOk<T,BOOL_1,void (T::*)(bool)> { static const bool value = true; };
template<typename T> struct SetterOk<T,INT_2,void (T::*)(int,int)> { static const bool value = true; };
template<typename T> struct SetterOk<T,FLOAT_2,void (T::*)(float,float)> { static const bool value = true; };
template<typename T> struct SetterOk<T,UINT_2,void (T::*)(unsigned int, unsigned int)> { static const bool value = true; };
template<typename T> struct SetterOk<T,CHAR_2,void (T::*)(char,char)> { static const bool value = true; };
template<typename T> struct SetterOk<T,BOOL_2,void (T::*)(bool,bool)> { static const bool value = true; };
template <bool, class T = void> struct enable_if {};
template <class T> struct enable_if<true, T> { typedef T type; };
template<typename T, Type Et>
struct Helper
{
template<typename U>
static void func(U method, typename enable_if<SetterOk<T,Et,U>::value>::type* dummy = 0)
{
}
};
class test
{
public:
void init()
{
Helper<test,INT_2>::func(&test::set);
}
void set2(int);
void set(int);
void set(int,int);
void set(float,float);
};
int main()
{
test t;
t.init();
return 0;
}
I'm expecting it to choose the right function between all possible. The problem is that the compiler says "cannot deduce template argument as function argument is ambiguous".
It seems I don't know how to use enable_if, because if so the compiler would only allow the specialization if the specified function has the right type...
Note that I want to have C++03 solutions (if possible) - my code must compile on some old compilers.
Thanks in advance
You can never refer to an overloaded function without disambiguating it (means: static_casting it to the correct type). When you instantiate Helper::func the type of the function argument cannot be known without ever disambiguating it.
The reason it doesn't compile is quite simply that there are several different overloaded functions and it doesn't know which one you mean. Granted, only one of these (void set(int,int)) would actually compile, given the specialization Helper<test,INT_2>. However, this is not enough for the compiler to go on.
One way of getting this to compile would be to explicitly cast &test::set to the appropriate type:
Helper<test,INT_2>::func(static_cast<void (test::*)(int,int)>(&test::set));
Another way would be to use explicit template specialization:
Helper<test,INT_2>::func<void (test::*)(int,int)>((&test::set));
Either way, you need to let the compiler know which of the set functions you are trying to refer to.
EDIT:
As I understand it, you want to be able to deduce, from the use of a Type, which function type should be used. The following alternative achieves this:
template<typename T, Type Et> struct SetterOK{};
template<typename T> struct SetterOK<T,INT_1> {typedef void (T::*setter_type)(int);};
template<typename T> struct SetterOK<T,FLOAT_1> {typedef void (T::*setter_type) (float);};
// ...
template<typename T> struct SetterOK<T,INT_2> {typedef void (T::*setter_type)(int,int);};
// ....
template<typename T, Type Et>
struct Helper
{
template<typename U>
static void func(U method)
{
}
};
class test
{
public:
void init()
{
Helper<test,INT_2>::func<SetterOK<test,INT_2>::setter_type >(&test::set);
}
void set2(int);
void set(int);
void set(int,int);
void set(float,float);
};
int main()
{
test t;
t.init();
return 0;
}
ADDITIONAL EDIT:
A thought just occurred to me. In this special case which you've done, where U is SetterOK::setter_type, things can be simplified further by completely removing the template arguments for func:
static void func(typename SetterOK<T,Et>::setter_type method)
{
}
This would make the init method a simpler:
void init()
{
Helper<test,INT_2>::func(&test::set);
}

Default parameters for methods of template class

Is there a way to provide default parameter values for methods of a template class? For example I have the following:
template<class T>
class A
{
public:
A foo(T t);
};
How should I modify this to give foo a default parameter of type T? For example: T is int then a default value of -23, or T is char* then default value of "something", etc. Is this even possible?
If you want the default parameter to be just the default value (zero, usually), then you can write A foo(T t = T()). Otherwise, I suggest a trait class:
template <typename T> struct MyDefaults
{
static const T value = T();
};
template <> struct MyDefaults<int>
{
static const int value = -23;
};
template<class T>
class A
{
public:
A foo(T t = MyDefaults<T>::value);
};
Writing the constant value inside the class definition only works for integral types, I believe, so you may have to write it outside for all other types:
template <> struct MyDefaults<double>
{
static const double value;
};
const double MyDefaults<double>::value = -1.5;
template <> struct MyDefaults<const char *>
{
static const char * const value;
};
const char * const MyDefaults<const char *>::value = "Hello World";
In C++11, you could alternatively say static constexpr T value = T(); to make the template work for non-integral values, provided that T has a default constructor that is declared constexpr:
template <typename T> struct MyDefaults
{
static constexpr T value = T();
};
template <> struct MyDefaults<const char *>
{
static constexpr const char * value = "Hello World";
};