Implementing std::visit

C++
Author

Quasar

Published

April 25, 2026

Introduction

std::visit(Visitor&&, Variants&&...) is a way to implement multiple dispatch in modern C++. std::visit expects a Visitor object and a pack of variants. Since each variant can hold one of many alternatives, the total number of distinguishable combinations are constexpr size_t n = std::variant_size<Variants> * .... The Visitor contains a callable - one for each possible combination of types of the variants. std::visit selects an appropriate overload, based on the actual combination of alternatives held in the variants from the n candidate functions.

A switcher struct

#include <cstdint>
#include <utility>
#include <numeric>
#include <variant>

namespace dev{
    namespace tools::v1{

        template<std::size_t size, typename Func>
        struct switcher{
            Func func_;
            using result_t = decltype(func_(std::integral_constant<std::size_t,0>()));
            constexpr switcher(std::integral_constant<std::size_t, size>, Func func) : func_{ func } {}

            constexpr result_t operator()(std::size_t i) const{
                using case_ = result_t(*)(Func);
                return [&]<std::size_t... idxs>(std::index_sequence<idxs...>){
                    // an array of anonymous functions
                    constexpr case_ cases[]{
                        [](Func func){ return func (std::integral_constant<std::size_t,idxs>()); }... 
                    };
                    return cases[i](func_);
                }
                (std::make_index_sequence<size>{});
            }
        };
    }
}

namespace dev{
    namespace tools::v2{
        template<std::size_t size, typename Func>
        struct switcher{
            Func func_;
            using res_t = decltype(func_(std::integral_constant<std::size_t, 0>()));
            switcher(std::integral_constant<std::size_t, size>, Func func) : func_{ func }{}

            constexpr res_t operator()(std::size_t i){
                return [&]<std::size_t... idxs>(std::index_sequence<idxs...>){
                    res_t result;
                    (((i == idxs) ? (result = func_(std::integral_constant<std::size_t, idxs>()), 0) : 0), ...);
                    return result;
                }(std::make_index_sequence<size>());
            }
        };
    }
}

Code Listing

// switch.h
#include <cstdint>
#include <utility>
#include <numeric>
#include <variant>

namespace dev{
    namespace tools::v1{

        template<std::size_t size, typename Func>
        struct switcher{
            Func func_;
            using result_t = decltype(func_(std::integral_constant<std::size_t,0>()));
            constexpr switcher(std::integral_constant<std::size_t, size>, Func func) : func_{ func } {}

            constexpr result_t operator()(std::size_t i) const{
                using case_ = result_t(*)(Func);
                return [&]<std::size_t... idxs>(std::index_sequence<idxs...>){
                    // an array of anonymous functions
                    constexpr case_ cases[]{
                        [](Func func){ return func (std::integral_constant<std::size_t,idxs>()); }... 
                    };
                    return cases[i](func_);
                }
                (std::make_index_sequence<size>{});
            }
        };
    }
}

namespace dev{
    namespace tools::v2{
        template<std::size_t size, typename Func>
        struct switcher{
            Func func_;
            using res_t = decltype(func_(std::integral_constant<std::size_t, 0>()));
            switcher(std::integral_constant<std::size_t, size>, Func func) : func_{ func }{}

            constexpr res_t operator()(std::size_t i){
                return [&]<std::size_t... idxs>(std::index_sequence<idxs...>){
                    res_t result;
                    (((i == idxs) ? (result = func_(std::integral_constant<std::size_t, idxs>()), 0) : 0), ...);
                    return result;
                }(std::make_index_sequence<size>());
            }
        };
    }
}

// Consider that we have variants v1, v2, v3 each having 2, 3, and 5 
// alternatives respectively. Then, we have the following:
// 0,0,0 maps to -> Case 0
// 0,0,1 maps to -> Case 1
// ...
// 0,1,0 maps to -> Case 5
// 0,1,1 maps to -> Case 6
// ...
// 0,2,4 maps to -> Case 14
// 1,0,0 maps to -> Case 15
// ...
// 1,2,4 maps to -> Case 29
// In general, (a,b,c) maps to -> 15a + 5b + c. 

// math::coeffs returns the coefficients array [15, 5, 1].
namespace dev::tools{
    template<std::size_t... dims>
    struct math_array{
        using multi_idx = std::array<std::size_t, sizeof...(dims)>;
        static constexpr multi_idx coeffs = [](){
            std::array dims_a = {dims...};
            std::array result = dims_a;
            for(std::size_t running = 1, i = dims_a.size(); i > 0; --i){
                result[i - 1] = running;
                running *= dims_a[i - 1];
            }
            return result;
        }();

        static constexpr multi_idx to_multi_idx(std::size_t linear_idx){
            std::array dims_a = {dims...};
            multi_idx result;
            multi_idx coeffs_ = coeffs();

            for(std::size_t i = 0; i < coeffs.size(); ++i){
                result[i] = linear_idx / coeffs_[i];
                linear_idx -= result[i];
            }
            return result;
        };

        static constexpr std::size_t to_linear_idx(multi_idx m_idx){
            std::size_t result{0uz};
            multi_idx coeffs_ = coeffs();
            result = std::inner_product(coeffs_.begin(), coeffs_.end(), m_idx.begin(), 0u);
            return result;
        }

        template<std::size_t linear_idx>
        static constexpr auto to_multi_idx_sequence(){
            return [&]<std::size_t... idxs>(std::index_sequence<idxs...>){
                constexpr auto arr = to_multi_idx(linear_idx);
                return std::index_sequence<arr[idxs]...>();
            }(std::make_index_sequence<sizeof...(dims)>());
        }
    };
};

std::size_t test(std::size_t init, std::size_t select){
    dev::tools::v2::switcher switch_{
        std::integral_constant<std::size_t, 10>{},
        [&]<std::size_t i>(std::integral_constant<std::size_t, i>){ return init + i; }
    };
    return switch_(select);
}

namespace dev{
    template<typename Visitor, typename... Variants>
    auto visit(Visitor&& visitor, Variants&&... variants){
        using c_math_array = tools::math_array<std::variant_size_v<Variants>...>;
        constexpr std::size_t num_cases = (std::variant_size_v<Variants> * ...);
        tools::v2::switcher switcher{
            std::integral_constant<std::size_t, num_cases>{},
            [&]<std::size_t case_>(std::integral_constant<std::size_t, case_>){
                constexpr auto seq = typename c_math_array::template to_multi_idx_sequence<case_>();
                return [&]<std::size_t... idxs>(std::index_sequence<idxs...>){
                    return std::forward<Visitor>(visitor)(std::get<idxs>(std::forward<Variants>(variants).data)...);
                }(seq);
            }
        };

        auto idx = c_math_array::to_linear_idx({variants.index...});
    }
}

Compiler Explorer