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

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

本文关键字:std visit libc++ libstdc++ 继承 variant      更新时间:2023-10-16

请考虑以下代码片段:

struct v : std::variant<int, std::vector<v>> { };
int main()
{
std::visit([](auto){ }, v{0});
}
  • 带有-stdlib=libc++ -std=c++2a的 clang++ 7 编译代码;

  • 带有-std=c++2a的 G++ 9 无法编译代码,并显示以下错误:

    /

    opt/compiler-explorer/gcc-trunk-20180711/include/c++/9.0.0/variant:94:29: 错误:嵌套名称中使用的类型"std::variant_size"不完整 规范

    inline constexpr size_t variant_size_v = variant_size<_Variant>::value;
    ^~~~~~~~~~~~~~
    

godbolt.org 上的活生生的例子


  • 两种实现是否符合标准?

  • 如果不是,这里的实现是正确的,为什么?

[variant.visit] 在 C++17 中没有使用variant_size_v,但由于编辑更改,它在当前的工作草案中确实使用了。我没有看到任何迹象表明 LWG 在进入之前审查了更改,但从那时起它已经多次查看了标准的这一部分,并且尚未反对它,所以我将假设它实际上是必需的。

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

看起来这是 gcc 实现中的一个错误。根据 cpp 偏好,它的称呼就像在std::get上调用invoke一样。std::get<>是为任何可转换为std::variant的东西定义的(因为它通过转发引用接受std::variant参数)。你的结构是可以转换为std::variant的,所以std::get本身在gcc中的结构上工作。

gcc 实现选择使用std::variant_size作为其实现visit的一部分这一事实是他们的实现细节,它不(也不应该)适用于您的结构这一事实无关紧要。

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

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

戈博尔特上的链接

它不漂亮,它将内容注入 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;
}