如何制作更安全的C++变体访问者,类似于switch语句?

Mic*_*ski 30 c++ visitor variant switch-statement c++17

许多人使用C++ 17/boost变体的模式看起来与switch语句非常相似.例如:( 来自cppreference.com的片段)

std::variant<int, long, double, std::string> v = ...;

std::visit(overloaded {
    [](auto arg) { std::cout << arg << ' '; },
    [](double arg) { std::cout << std::fixed << arg << ' '; },
    [](const std::string& arg) { std::cout << std::quoted(arg) << ' '; },
}, v);
Run Code Online (Sandbox Code Playgroud)

问题是当您在访问者中输入错误的类型或更改变体签名时,但忘记更改访问者.您将获得错误的lambda,通常是默认的lambda,而不是获得编译错误,或者您可能会得到一个您没有计划的隐式转换.例如:

v = 2.2;
std::visit(overloaded {
    [](auto arg) { std::cout << arg << ' '; },
    [](float arg) { std::cout << std::fixed << arg << ' '; } // oops, this won't be called
}, v);
Run Code Online (Sandbox Code Playgroud)

关于枚举类的switch语句更安全,因为您不能使用不属于枚举的值来编写case语句.同样,我认为如果变体访问者仅限于变体中包含的类型的子集,加上默认处理程序,那将非常有用.有可能实现这样的东西吗?

编辑:s /隐式转换/隐式转换/

EDIT2:我想有一个有意义的catch-all [](auto)处理程序.我知道如果不处理变体中的每个类型,删除它将导致编译错误,但这也会从访问者模式中删除功能.

Hol*_*olt 25

如果您只想允许类型的子集,那么您可以static_assert在lambda的开头使用a ,例如:

template <typename T, typename... Args>
struct is_one_of: 
    std::disjunction<std::is_same<std::decay_t<T>, Args>...> {};

std::visit([](auto&& arg) {
    static_assert(is_one_of<decltype(arg), 
                            int, long, double, std::string>{}, "Non matching type.");
    using T = std::decay_t<decltype(arg)>;
    if constexpr (std::is_same_v<T, int>)
        std::cout << "int with value " << arg << '\n';
    else if constexpr (std::is_same_v<T, double>)
        std::cout << "double with value " << arg << '\n';
    else 
        std::cout << "default with value " << arg << '\n';
}, v);
Run Code Online (Sandbox Code Playgroud)

如果您在变体中添加或更改类型,或者添加一个类型,则这将失败,因为T需要恰好是给定类型之一.

您也可以使用您的变体std::visit,例如使用"默认"访问者,例如:

template <typename... Args>
struct visit_only_for {
    // delete templated call operator
    template <typename T>
    std::enable_if_t<!is_one_of<T, Args...>{}> operator()(T&&) const = delete;
};

// then
std::visit(overloaded {
    visit_only_for<int, long, double, std::string>{}, // here
    [](auto arg) { std::cout << arg << ' '; },
    [](double arg) { std::cout << std::fixed << arg << ' '; },
    [](const std::string& arg) { std::cout << std::quoted(arg) << ' '; },
}, v);
Run Code Online (Sandbox Code Playgroud)

如果添加一个类型,它是不是一个int,long,doublestd::string,然后visit_only_for调用操作将被匹配,你将有一个模糊的调用(这一个,默认的之间).

这也应该没有默认值,因为visit_only_for调用运算符将​​匹配,但由于它被删除,您将得到编译时错误.

  • `visit_only_for`可能会删除其运算符.(它会避免引入虚拟`ret_t`). (3认同)