What is tag-dispatch in C++?

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.

Subscribe

I notify you of my new posts

Latest Posts

Comments

0 comment