【问题标题】:Derive from `boost::static-visitor` to remove code duplication从 `boost::static-visitor` 派生以删除代码重复
【发布时间】:2026-01-15 12:55:02
【问题描述】:

在我的一个项目中,我过度使用了boost-variant。在某些时候,我超出了boost-variant 的模板参数的最大数量 (20)。因此,我通过将多个 boost-variant 类型像链表一样链接在一起,得出了以下解决方案。

#include <boost/variant.hpp>
#include <iostream>

template<int T> struct A {
    const int value = T;
};

typedef boost::variant<
    A<20>,A<21>,A<22>,A<23>,A<24>,A<25>,A<26>,A<27>,A<28>,A<29>,A<30>,A<31>,A<32>,A<33>,A<34>,A<35>,A<36>,A<37>,A<38>,A<39>
> NextVar;

typedef boost::variant<
    A<1>,A<2>,A<3>,A<4>,A<5>,A<6>,A<7>,A<8>,A<9>,A<10>,A<11>,A<12>,A<13>,A<14>,A<15>,A<16>,A<17>,A<18>,A<19>,NextVar
> TVar;

struct PrintVisitor : public boost::static_visitor<std::string> {
    result_type operator()(const NextVar& n) {
        return n.apply_visitor(*this);
    }

    template<int T>
    result_type operator()(const A<T>& a)  {
        return std::to_string(a.value);
    }
};

struct IntVisitor : public boost::static_visitor<int> {
    result_type operator()(const NextVar& n) {
        return n.apply_visitor(*this);
    }

    template<int T>
    result_type operator()(const A<T>& a) {
        return a.value;
    }
};

template<int I>
struct AddVisitor : public boost::static_visitor<int> {
    result_type operator()(const NextVar& n) {
        return n.apply_visitor(*this);
    }

    template<int T>
    result_type operator()(const A<T>& a) {
        return a.value+I;
    }
};

int main(int argc, char **args) {
    TVar x = A<35>(); 
    PrintVisitor v1;
    std::cout << x.apply_visitor(v1) << std::endl;
    IntVisitor v2;
    std::cout << x.apply_visitor(v2) << std::endl;
    AddVisitor<10> v3;
    std::cout << x.apply_visitor(v3) << std::endl;
}

我真的很惊讶这种解决方法能很好地解决我的问题。仍然有一粒盐。对于每个访问者,我必须包含以下行:

result_type operator()(const NextVar& n) {
    return n.apply_visitor(*this);
}

这似乎是一种不必要的代码重复。更糟的是,如果我需要 boost-variant 中的 60 种甚至更多类型。我的尝试是为所有访问者定义一个公共基类:

template<typename T>
struct BaseVisitor : public boost::static_visitor<T> {
    result_type operator()(const NextVar& n) {
        return n.apply_visitor(*this);
    }
};

我认为,从BaseVisitor 派生如下所示可以解决问题:

struct PrintVisitor : public BaseVisitor<std::string> {
    template<int T>
    result_type operator()(const A<T>& a)  {
        return std::to_string(a.value);
    }
};

但是编译器却抱怨:

template-argument for "const A<T> &" could not be derived from "T19" 

对于此类问题,最接近的解决方法是什么?

【问题讨论】:

    标签: c++ templates boost boost-mpl boost-variant


    【解决方案1】:

    首先,您可以简单地增加 BOOST_MPL_LIMIT_LIST_SIZE 固定的 20 的限制。

    关于您的代码:即使它可以编译,BaseVisitor::operator() 也会进行无限递归,因为此时 *this 被视为 BaseVisitor。
    为避免这种情况,您可以使用 CRTP 来代替 derived()

    template<class Derived, typename T>
    struct BaseVisitor : public boost::static_visitor<T> {
        using typename boost::static_visitor<T>::result_type;
    
        Derived & derived() { return static_cast<Derived &>(*this); } 
    
        result_type operator()(const NextVar& n) {
            return n.apply_visitor( derived() );
        }
    };
    

    然后将相同的operator() 带入派生类的范围(否则被新的范围隐藏)(以及模板类所需的result_type)。

    DEMO

    没有别名

    正如 cmets 中所说,别名必须写在每个派生类中。为了摆脱它,我们可以将两个operator()s 集合在基类中的同一级别,并将派生函数命名为不同的名称(此处为visit):

    template<class Derived, typename T>
    struct BaseVisitor : public boost::static_visitor<T> {
        using typename boost::static_visitor<T>::result_type;
    
        Derived & derived() { return static_cast<Derived &>(*this); } 
    
        result_type operator()(const NextVar& n) {
            return n.apply_visitor( derived() );
        }
        template<int I>
        result_type operator()(const A<I>& a)  {
            return derived().visit(a);
        }
    };
    

    留给我们:

    struct PrintVisitor : public BaseVisitor<PrintVisitor, std::string> {
        template<int I>
        std::string visit(const A<I>& a)  {
            return std::to_string(a.value);
        }
    };
    

    DEMO

    【讨论】:

    • 感谢您的快速回复和您的好解决方案。不幸的是,我有一些限制,不能使用BOOST_MP_LIMIT_LIST_SIZE。您的解决方案肯定有效。 CRTP 对我来说仍然不是那么明显。 :-) 我想知道是否可以去除派生类中的杂乱(模板别名代码)?
    • @FrankSimon 除了演示的第 31 和 44 行(不是模板类),没有。但是通过稍微修改我们的代码......查看版本。
    • 真的很棒。我需要一些时间来消化你的解决方案。非常感谢。我将在我的生产代码中使用它。