Reputation: 75
I'm trying write e generic component that handle data, identified currently by an enum. The data can be of different types but one id is only of one type.
What I'm trying to achieve is to specialize the template somewhere in my code base and call a function by their identifier only, like
Test::get<ID2>()
What works in the code below including the type. Is there a simple/smart way to avoid writing the type in get line?
I tried to use a typedef for the identification, buy of course that gets directly resolved to the primitive type and the two template specializations are ambiguous. If there is no good C++ way, I can still use code generation to solve that but I hope for a better solution.
#include <iostream>
typedef int ID1_t;
typedef double ID2_t;
enum {
ID1,
ID2,
};
class Test
{
public:
template<int I, typename T> static T get();
};
template<> int Test::get<ID1>()
{
return 43;
}
template<> double Test::get<ID2>()
{
return 0.12;
}
int main()
{
std::cout << Test::get<ID2, ID2_t>() << std::endl;
std::cout << Test::get<ID1, ID1_t>() << std::endl;
}
EDIT (a bit more Background): I'm writing a central to use component to get data (like an int) and set data to in with a notification mechanism to inform all needed places. Its central defined but de-central implemented, that the interface is the same for all uses but the implementation can be in an own source file. That reduces dependencies between components a lot but also gives a strong typed data exchange.
For example:
Lets say we have data written in a FLASH memory at production time, having configuration data stored in an EEPROM or FRAM and also some volatile data in RAM variables that will be forgotten on power cycle.
Lets take a volume value for a loudspeaker that can be changed somehow. I just want to put the value into the system:
auto volume = 55; //(constant here but is in real decoded from SPI)
CentralDataStorage::set<Volume>(volume);
I don't want to make a dependency to the physical storage here. Than I want a component that is responsible to store the value as well as return the value on request. So it implements the set and get method but only for the id Volume.
Another Component the one that actually set the volume into the amplifier, gets a notification and uses the get method from the Central interface which is implemented somewhere this component does not know.
auto volume = CentralDataStorage::get<Volume>();
Everything is known at compile time and needs to be type safe.
Upvotes: 2
Views: 195
Reputation: 41750
I'll explain first my preferred way to do this kind of stuff. I usually try to avoid enum since almost everytime you need additional data, and with enum you're stuck writing that data elsewhere. For example, that elsewhere may be in template specialization.
I would rather use types:
struct ID1 {
static constexpr auto default_value = 42;
};
struct ID2 {
static constexpr auto default_value = 0.12;
};
Then drop the specialization and use the data provided in the types:
struct Test {
template<typename T>
static auto get() {
// or maybe call a static member function
return T::default_value;
}
};
int main()
{
std::cout << Test::get<ID2>() << std::endl;
std::cout << Test::get<ID1>() << std::endl;
}
But there is a way to make your design work and avoid writing that type using return type deduction:
#include <iostream>
enum {
ID1,
ID2,
};
struct Test {
// to be deduced -------v
template<auto I> static auto get();
// ^--- (optional change)
// Have the enum type deduce as well
};
template<> auto Test::get<ID1>()
{
return 43;
}
template<> auto Test::get<ID2>()
{
return 0.12;
}
int main()
{
std::cout << Test::get<ID2>() << std::endl;
std::cout << Test::get<ID1>() << std::endl;
}
See it compiling on compiler explorer.
You could also revert back to int
as the enum template parameter be stay compatible with C++14:
template<int I> static auto get();
// ^------ reverted it back to int
But using C++17 and auto template parameters, you can have multiple enum without value conflict:
enum struct A {
Value1, Value2
};
enum struct B {
Value1
};
// Different with C++17 auto template parameter
// not possible with C++14
template<> auto Test::get<A::Value1>()
{
return 43;
}
template<> auto Test::get<B::Value1>()
{
return 0.12;
}
Upvotes: 2
Reputation: 75
I found a way using parts of the other answers and show the implementation here:
central header file to define all Ids and types. decltype helps finding the return type. Using auto here leads me to the problem that when I declare the method here for the specific type, would not match the template declaration
//Test.hpp
struct ID1 {
int value;
};
struct ID2 {
double value;
};
struct Test {
template<typename T> static decltype(T::value) get();
};
Then I have the definition of the get function (which can also be in separate compilation units)
#include "Test.hpp"
template<> int Test::get<ID1>()
{
return 43;
}
template<> double Test::get<ID2>()
{
return 0.12;
}
and a main that uses them
#include "Test.hpp"
int main()
{
std::cout << Test::get<ID2>() << std::endl;
std::cout << Test::get<ID1>() << std::endl;
}
This is pretty much taken the answers from George Kourtis and Guillaume Racicot together and put another bit in myself.
Upvotes: 2
Reputation: 2592
I suppose you wanted this ?
#include <iostream>
typedef int ID1;
typedef double ID2;
class Test
{
public:
template<typename T> static T get();
};
template<> int Test::get<ID1>()
{
return 43;
}
template<> double Test::get<ID2>()
{
return 0.12;
}
int main()
{
std::cout << Test::get<ID2>() << std::endl;
std::cout << Test::get<ID1>() << std::endl;
}
The idea is that if the data type is known at compile time, then there is no need to write the number in order to select the kind of data. The data type itself is sufficient in order to do the job.
Upvotes: 1
Reputation: 11940
Do you mean, split declaration in two?
template<int i> struct id_type;
template<int i> using id_type_t = typename id_type<i>::type;
template<> struct id_type<ID1> { using type = int; }
template<int i> id_type_t<i> get();
Upvotes: 0