Blood-HaZaRd
Blood-HaZaRd

Reputation: 2138

Usage of empty structs in C++

In some code that I was reading, I found the usage of empty struct like so:

struct input_iterator_tag { };
struct bidirectional_iterator_tag { };
struct random_access_iterator_tag { };

So in the rest of the code, it was used as what they call tag dispatching.

I was wondering if there is other usage of empty structs.

from an older post I saw that :

three major reasons we use empty structs in C++ are:

  • a base interface
  • a template parameter
  • a type to help overload resolution. (tag dispatching if I am not wrong)

Could someone explain that please?

Upvotes: 4

Views: 2952

Answers (2)

einpoklum
einpoklum

Reputation: 132310

a type to help overload resolution. (tag dispatching if I am not wrong)

When you want to use a complex template specialization pattern on some function, you don't try to go at it directly, but rather write:

template <typename T1, typename T2, other things maybe>
int foo(T1 param1, T2 param2 and so on)
{
    using tag = put your complex stuff here, which produces an empty struct
    detail::foo_impl(tag, std::forward<T1>(param1), std::forward<T2>(param2) and so on);
}

Now, the compiler doesn't have to decide between competing choices of template specialization, since with different tags you get incompatible functions.

a base interface

struct vehicle { 
    // common members and methods,
    // including (pure) virtual ones, e.g.
    virtual std::size_t num_maximum_occupants() = 0;
    virtual ~vehicle() = default;
};

namespace mixins {
struct named { std::string name; };
struct wheeled { int num_wheels; public: rev() { }; };

}  // namespace mixins
    
struct private_sedan : public vehicle, public wheeled, named {
   // I dunno, put some car stuff here
   //
   // and also an override of `num_maximum_occupants()`
};

Making the base struct completely empty is perhaps not that common, but it's certainly possible if you use mixins a lot. And you could check for inheritance from vehicle (although I'm not sure I'd do that).

a template parameter

Not sure what this means, but venturing a guess:

template <typename T>
struct foo { };

template <typename T, typename N>
struct foo<std::array<T, N>> {
    int value = 1;
};

If you now use foo<T>::value in a function, it will work only if T is int with few (?) exceptions.

Upvotes: 4

KamilCuk
KamilCuk

Reputation: 142005

I also tried to come up with examples:

as a base interface

// collection of very abstract vehicles
#include <vector>

struct Vehicle {};
struct Car : Vehicle {
    int count_of_windows;
};
struct Bike : Vehicle {
    int size_of_wheels;
};

std::vector<Vehicle> v{Bike{}, Car{}};

as a template parameter

// print same number in 3 different formats

#include <iostream>

struct dec {};
struct hex {};
struct octal {};

template<typename HOW = dec>
void print_me(int v);

template<>
void print_me<dec>(int v) {
    auto f = std::cout.flags();
    std::cout << std::dec << v << std::endl;
    std::cout.flags(f);
}

template<>
void print_me<hex>(int v) {
    auto f = std::cout.flags();
    std::cout << std::hex << v << std::endl;
    std::cout.flags( f );
}

template<>
void print_me<octal>(int v) {
    auto f = std::cout.flags();
    std::cout << std::oct << v << std::endl;
    std::cout.flags(f);
}

int main() {
  print_me(100);
  print_me<hex>(100);
  print_me<octal>(100);
}

a type to help overload resolution

// add a "noexcept" qualifier to overloaded function
// the noexcept version typically uses different functions
// and a custom "abort" handler

#include <iostream>

struct disable_exceptions {};

void is_number_1() {
    int v;
    std::cin >> v;
    if (v != 1) {
        throw new std::runtime_error("AAAA");
    }
}

void is_number_1(disable_exceptions) noexcept {
    int v;
    // use C function - they don't throw
    if (std::scanf("%d", &v) != 1) {
        std::abort();
    }
    if (v != 1) {
        std::abort();
    }
}

int main() {
    is_number_1();
    is_number_1(disable_exceptions());
}

The example about "tag dispatching" can be found on cppreference iterator_tags. The iterator_category() member of an iterator is used to pick a different overload. That way you could write a different algorithm if for example iterator is forward_iterator, where you can only go forward, or it is a bidirectional_iterator, where your algorithm could change because you may walk back.

Upvotes: 2

Related Questions