在继承自 std::variant 的 class 上使用 std::visit - libstdc++ 与 libc++

Using std::visit on a class inheriting from std::variant - libstdc++ vs libc++

考虑以下代码片段:

struct v : std::variant<int, std::vector<v>> { };

int main()
{
    std::visit([](auto){ }, v{0});
}

live example on godbolt.org


看起来这是 gcc 实现中的错误。根据 cppreference,它的调用方式如同在 std::get 上调用 invokestd::get<> 是为任何可转换为 std::variant 的东西定义的(因为它通过转发引用接受 std::variant 参数)。您的结构可转换为 std::variant,因此 std::get 本身适用于您在 gcc 中的结构。

gcc 实现选择使用 std::variant_size 作为其实现 visit 的一部分这一事实是它们的实现细节,而事实上它没有(也不应该)为你的结构工作是无关紧要的。

结论:这是 gcc 中的一个错误,由于在实现中的疏忽。

[variant.visit] in C++17 doesn't use variant_size_v, but it does in the current working draft as a result of an editorial change。我没有看到任何迹象表明 LWG 在更改之前审查了更改,但从那时起它已经多次查看了标准的这一部分并且尚未反对它,所以我假设它在需要事实。

同时,已提交给 LEWG 的 LWG issue 3052 将明确要求 std::variant。当该问题得到解决时 - 无论哪种方式 - 它也应该解决这个问题。

我最近也遇到了这个问题。我想出了一个解决方法,它基本上专门针对从变体继承的 class variant_size 和 variant_alternative ..

link on godbolt

它不漂亮,它向 std 命名空间注入了一些东西。我不是元编程专家(还不是!)所以它是我一起破解的。也许其他人可以对此进行改进?

#include <variant>
#include <string>
#include <vector>
#include <iostream>

#include <utility>
#include <type_traits>



using var = std::variant<int, bool, float, std::string>;

struct myvar : public var {
    using var::var;
    using var::operator=;

};

namespace std{

    template<>
    struct variant_size<myvar> : variant_size<var> {
    };

    template<std::size_t I>
    struct variant_alternative<I,myvar> :  variant_alternative<I,var> {
    };
}

int main(){

    constexpr int vs = std::variant_size<var>::value;

    myvar s = std::string{"boo!"}; 
    std::visit([](auto&& e){std::cout << e << "\n";}, s);
    std::cout << vs;
}