Skip to content

Instantly share code, notes, and snippets.

@dmh2000
Last active October 6, 2015 22:51
Show Gist options
  • Save dmh2000/799bf0472527b7a654c1 to your computer and use it in GitHub Desktop.
Save dmh2000/799bf0472527b7a654c1 to your computer and use it in GitHub Desktop.
kind of simple example for how 'traits' work
#include <cstdio>
#include <cstdint>
#include <limits>
/**
USING TAGGED DISPATCH FOR GENERIC IF-THEN-ELSE
Or, sort of a contrived example that illustrates more or less how 'tagged disppatch' in the STL work
*/
/** PROBLEM :
We are writing some library routines that will be consumed by various clients.
We need two functions, one that has an integer input argument
and one that has a double input argument. Most of the code for the two
versions is identical except for one piece. That part is different based
on whether the the top level input argument is integer or double.
We decide to write two overloads of the function, one that is overloaded for integers
and the other for double. This is a typical approach, but it has a drawback:
it requires a lot of duplicated code.
yes we could factor out the identical parts as much as possible but having two versions
is still kind of klunky.
tagged dispatch is supported by the std::traits templates
*/
// this represents a configuration parameter that might be different for different builds.
const int32_t CONFIGURATION_PARAMETER = 1;
// ============================================
// versions selected by name
// (can't be overloaded by return value only)
// ============================================
// version called by integer clients
int32_t fi(int32_t config) {
// use the configuration
printf("fi(int32_t)\n");
return 0; // return whatever results
}
// version called by floating point clients
double ff(int32_t config) {
printf("ff(double)\n");
return 0.0; // return whatever results
}
// can't use overloading because fi and ff only differ by return type
// so have to implement two versions
// functions fa(int32_t) and fa(double) are identical except for the
// calls to 'fi' and 'ff' depending on the type
// INTEGER VERSION
int32_t fa(int32_t i) {
int32_t k;
// =======================
// .... lots of code here
// =======================
k = fi(CONFIGURATION_PARAMETER); // call integer version
// =======================
// .... lots of code here
// =======================
return k;
}
// FLOATING POINT VERSION
double fa(double f) {
double k;
// =======================
// .... lots of code here
// =======================
k = ff(CONFIGURATION_PARAMETER); // call float version
// =======================
// .... lots of code here
// =======================
return k;
}
// ====================================================================================
// because 'ff' and 'fi' differ only by return type (they both have an integer as their
// only parameter), we can't use an overload to call the right ones.
// So here's what we would like to have is a generic
// template that executes the proper code based on the parameter type
// ====================================================================================
/*
template<typename T> T f(T t) {
T k;
// =======================
// .... lots of code here
// =======================
// how do you do this if your input 'T' can't be used to
// resolve an overload.
if (T is an integer type) {
k = fi(CONFIGURATION_PARAMETER); // call the integer version
}
else if (T is a floating point type)
k = ff(CONFIGURATION_PARAMETER) // call the floating point version
}
// =======================
// .... lots of code here
// =======================
return k;
}
*/
// =================================================================
// GENERIC VERSION
// =================================================================
/**
It is possible to write a generic template, using the 'tagged dispaatch' convention
to create the equivalent of an if-then-else based on types rather than values.
Proceed as follows:
*/
// step 1 : define a traits tag struct for each alternative
// any name is ok
struct integer_traits_tag {};
struct double_traits_tag {};
// step 2 : create a generate 'traits' type
template<typename T> struct number_traits {
// generic version has no innards
};
// step 3a : specialize the traits type for integer
template<> struct number_traits<int32_t> {
// declare a typedef 'type' for this specialization using the appropriate traits tag
typedef integer_traits_tag type;
};
// step 3b : specialize the traits type for double
template<> struct number_traits<double> {
// declare a typedef 'type' for this specialization using the appropriate traits tag
typedef double_traits_tag type;
};
// step 4: create two overloaded functions, one for the integer version and one for the floating point version.
// differentiate them by the traits tag, which will be used to force the correct overload
int32_t fx(int32_t config, integer_traits_tag)
{
// INTEGER VERSION
// do something with 'config' and return a value
printf("fx<int32_t>\n");
return 1;
}
double fx(int32_t config, double_traits_tag)
{
// FLOATING POINT VERSION
// do something with 'config' and return a value
printf("fx<double>\n");
return 0.0;
}
// since the two versions have a lot of duplicated code,
// we want a generic version that calls the right function based on type and eliminate the extra code.
template<typename T> T fb(T t) {
T k;
// =======================
// .... lots of code here
// =======================
// force the correct overload by the traits type argument
k = fx(CONFIGURATION_PARAMETER, number_traits<T>::type());
// =======================
// .... lots of code here
// =======================
return k;
}
int main(int argc, char *argv[])
{
int32_t a;
double b;
a = fa(1);
b = fa(1.0);
a = fb(1);
b = fb(1.0);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment