Introduction
Tag-dispatch is a metaprogramming technique to overload a function using different tag parameters. The right overload is chosen by a compiler at compile time. It can be replaced by if-constexpr
of C++17.
Background
Sometimes we write a function that can be run in different ways. By giving an option number, we can tell which way is taken:
auto h0(){}
auto h1(){}
auto h2(){}
auto h(int option){
if (option==0)
h0();
else if(option==1)
h1();
else
h2();
}
During runtime we can change options and different functions are called. Let’s see if we can do this at compile time.
Definition
We can overload a function in a way that, giving a tag, the right overload is chosen.
For example:
struct tag1{};
struct tag2{};
auto f(int a, tag1 dummy){
std::cout<< a << "via tag1 \n";
}
auto f(int a, tag2 dummy){
std::cout<< a << "via tag2 \n";
}
In the example above, the main parameter is a
, and tag type is just there to choose the right overload. Therefore,
f(1, tag1{}); // calls first case
f(1, tag2{}); // calls second case
This is tag-dispatch. For this to work, tags must be of different types. It’s usual to put the tag as the last parameter.
Boolean tag
Standard library has true_type
and false_type
which are types not values. We can use them in boolean tag-dispatch:
auto g(int a, std::true_type t){
std::cout<< a << "via true type \n";
}
auto g(int a, std::false_type f){
std::cout<< a << "via false type \n";
}
g(1, std::true_type{}); // first case is called
g(1, std::false_type{}); // second case is called
We know types can be programmed at compile time which is called metaprogramming. Now let’s make use of g()
at compile time. We have a function that tells us if a number is odd or even:
template<int i>
auto IsEven(){
if constexpr(i%2==0)
return std::true_type{};
else
return std::false_type{};
}
Now we can call the g()
versions based on if the input is odd or even:
g(1, IsEven<11>()); // second g() is called
g(1, IsEven<20>()); // first g() is called
So the user of our library, just put the number in and the compiler finds the right overload of g()
.
Struct to Tuple
Let’s use the idea in a more practical case. We know that the members of a struct, here s
, can be binded like:
struct S{/*members...*/}
S s;
auto [m0, m1, m2] = s;
Using this we can create a tuple out of the members like
std::make_tuple(m0, m1, m2);
But the challenge is that to program this to work with structs including different number of members. That’s when tag-dispatch helps us:
template<int i>
struct int_const{};
// for struct with 1 member
template<typename S>
auto Struct2Tuple(S s, int_const<1> count){
auto [m0] = s;
return std::make_tuple(m0);
}
// for struct with 2 members
template<typename S>
auto Struct2Tuple(S s, int_const<2> count){
auto [m0,m1] = s;
return std::make_tuple(m0, m1);
}
// more overloads for more members...
int_const
: It is a template type dependent on i. In other words, it is a wrapper to show numbers as types. Therefore, int_const<1>, int_const<2>, int_const<3>, … are different types but at the same time they represent counts.Struct2Tuple
: a function overloaded with different tags, int_const<1>, int_const<2>, … where each tag represents the number of struct members. I just wrote two overloads, I leave the rest to you.
Now let’s test it:
struct Person{
int age;
double height;
};
int main(){
auto a = Person{25, 1.80};
auto t = Struct2Tuple(a, int_const<2>{}); // person has 2 members
std::cout<<std::get<0>(t); // 25
std::cout<<std::get<1>(t); // 1.8
return 0;
}
Overload function with return type
It’s not possible to overload a function only based on its return type. But with a tag-dispatch technique, we can pass the return type as the last parameter to be the tag:
std::string r(int a, std::string dummy ){
return "string returned,"+std::to_string(a);
}
int r(int a, int dummy ){
return a;
}
int main(){
std::cout<<r(1, std::string{});
std::cout<<r(1, int{});
return 0;}
if-constexpr vs tag-dispatch
Instead of overloading a function for tag-dispatch, alternatively we can write one function template including an if-constexpr
(C++17) block assessing different types:
template<typename Tag>
auto f(/*parameters*/){
if constexpr (/*Tag is something*/)
/* do this */
else if constexpr (/*Tag is another thing*/)
/* do that */
else
/* do default */
}
As desired, if-constexpr
eliminates the code of unmet conditions from the compiled program. Therefore, both tag-dispatch and if-constexpr
can do the same job and it’s a matter of the programmer taste and C++ standard. Anyhow, probably tag-dispatch is neater and safer for not letting unwanted code to leak into the compiled program.
Latest Posts
- A C++ MPI code for 2D unstructured halo exchange
- Essential bash customizations: prompt, ls, aliases, and history date
- Script to copy a directory path in memory in Bash terminal
- What is the difference between .bashrc, .bash_profile, and .profile?
- A C++ MPI code for 2D arbitrary-thickness halo exchange with sparse blocks