std::visit如何与std::variant一起工作?

29
我正在查看这里的std:variant/std::visit文档:http://en.cppreference.com/w/cpp/utility/variant/visit,并且也通过谷歌搜索了很多,试图理解std::visitstd::variant背后的魔力。
所以我的问题是:在提供的示例中,在多态lambda表达式和“重载函数对象”中都有一些“魔法”,使得从std::variant中提取正确类型成为可能。
因此,看着这个:
for (auto& v: vec) {
    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);
}

对于每个变量v,如何调用正确的重载lambda函数?似乎有一些逻辑需要确定特定std::variant持有的确切类型,将其强制转换并将其分派到正确的函数。我的问题是它是如何工作的?对于这个问题也是同样的情况:
    std::visit([](auto&& arg) {
        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, long>)
            std::cout << "long with value " << arg << '\n';
        else if constexpr (std::is_same_v<T, double>)
            std::cout << "double with value " << arg << '\n';
        else if constexpr (std::is_same_v<T, std::string>)
            std::cout << "std::string with value " << std::quoted(arg) << '\n';
        else 
            static_assert(always_false<T>::value, "non-exhaustive visitor!");
    }, w);

我们将多态lambda作为可调用对象传递给访问者,w是一些可以容纳int、long、double或std::string的变量。那么,哪里有逻辑来确定正确的类型呢?因此,使用using T = std::decay_t<decltype(arg)>;来检索特定实例的实际类型。

你是在询问std::visit的实现吗? - cpplearner
@cpplearner - 可能 :) - Kobi
11
https://mpark.github.io/programming/2015/07/07/variant-visitation/ - Casey
@Casey 谢谢。看起来 index() 和 get<I> 就是上面的答案。感谢提供的链接! - Kobi
1个回答

23
我认为,std::visit在底层构建了一个函数指针数组(在编译时),其中包含每个类型的实例化函数指针。variant存储了一个运行时类型索引i(整数),这使得选择正确的第i个函数指针并注入值成为可能。
你可能会想知道如何在编译时将具有不同参数类型的函数指针存储在数组中?-> 这是通过类型抹除(我认为)完成的,这意味着将函数存储为带有例如void * 参数的函数,例如&A<T>::call
template<typename T>
struct A
{
   static call(void*p) { otherFunction(static_cast<T*>(p)); } 
}

每个call都会将参数分派到正确的函数otherFunction中(这是在代码结尾处定义的lambda函数)。 类型擦除意味着函数auto f = &A<T>::call不再具有类型T,其签名为void(*)(void*)std::variant非常复杂且相当成熟,其中涉及许多强大而复杂的元编程技巧。本回答可能只涵盖了冰山一角 :-)

类型擦除不是必需的。根据编译器输出中的符号名称(libstdc++),函数指针数组实际上是一组静态调用者对象的静态数组,这些对象将对应于调用者模板参数之一的std::get<T>函数调用每个变体。然后,同一个可调用对象由所有调用者在转换后的变体上使用。如果可调用对象是一个lambda表达式(auto),编译器将为每种情况专门化该lambda表达式。否则,在绑定参数时将会进行常规的隐式转换。 - Meatboy 106

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接