在继承自 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});
}
clang++ 7用-stdlib=libc++ -std=c++2a
编译代码;
g++ 9 with -std=c++2a
无法编译代码,出现以下错误:
/opt/compiler-explorer/gcc-trunk-20180711/include/c++/9.0.0/variant:94:29:
error: incomplete type 'std::variant_size' used in nested name
specifier
inline constexpr size_t variant_size_v = variant_size<_Variant>::value;
^~~~~~~~~~~~~~
两种实现都符合标准吗?
如果不是,这里哪种实现是正确的,为什么?
看起来这是 gcc 实现中的错误。根据 cppreference,它的调用方式如同在 std::get
上调用 invoke
。 std::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 ..
它不漂亮,它向 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;
}
考虑以下代码片段:
struct v : std::variant<int, std::vector<v>> { };
int main()
{
std::visit([](auto){ }, v{0});
}
clang++ 7用
-stdlib=libc++ -std=c++2a
编译代码;g++ 9 with
-std=c++2a
无法编译代码,出现以下错误:/opt/compiler-explorer/gcc-trunk-20180711/include/c++/9.0.0/variant:94:29: error: incomplete type 'std::variant_size' used in nested name specifier
inline constexpr size_t variant_size_v = variant_size<_Variant>::value; ^~~~~~~~~~~~~~
两种实现都符合标准吗?
如果不是,这里哪种实现是正确的,为什么?
看起来这是 gcc 实现中的错误。根据 cppreference,它的调用方式如同在 std::get
上调用 invoke
。 std::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 ..
它不漂亮,它向 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;
}