GNU C++ std::variant实现变更相关编译问题咨询
问题背景
我有如下使用std::variant的示例代码:
#include <iostream> #include <string> #include <variant> class Cat { public: const std::string& getSound() const { return sound; }; private: std::string sound = "Meow."; }; class Dog { public: const std::string& getSound() const { return sound; }; private: std::string sound = "Bark."; }; class House { public: void resetAnimal(const auto&& animal) { v = std::move(animal); } const auto& getAnimal() const { return std::visit([](const auto& animal) -> decltype(animal)& { return animal; }, v); } private: std::variant<Cat, Dog> v; }; int main() { House house; house.resetAnimal(Cat()); std::cout << house.getAnimal().getSound() << std::endl; house.resetAnimal(Dog()); std::cout << house.getAnimal().getSound() << std::endl; return 0; }
这段代码用g8到g10版本,编译参数加-std=c++17和-fpermissive可以正常编译,运行后会分两行输出Meow.和Bark.,但用g++11及以上版本编译会报错,报错信息如下:
In file included from <source>:3: /opt/compiler-explorer/gcc-11.1.0/include/c++/11.1.0/variant: In instantiation of 'constexpr decltype(auto) std::visit(_Visitor&&, _Variants&& ...) [with _Visitor = House::getAnimal() const::<lambda(const auto:23&)>; _Variants = {const std::variant<Cat, Dog>&}]': <source>:25:26: required from here /opt/compiler-explorer/gcc-11.1.0/include/c++/11.1.0/variant:1758:29: error: static assertion failed: std::visit requires the visitor to have the same return type for all alternatives of a variant 1758 | static_assert(__visit_rettypes_match, | ^~~~~~~~~~~~~~~~~~~~~~ /opt/compiler-explorer/gcc-11.1.0/include/c++/11.1.0/variant:1758:29: note: '__visit_rettypes_match' evaluates to false <source>: In member function 'const auto& House::getAnimal() const': <source>:26:48: error: forming reference to void 26 | { return animal; }, v); | ^ Compiler returned: 1
用MSVC编译也会得到含义相近的报错,针对相关问题解答如下:
1. 这段示例代码是否可以使用g++11编译?若可以,需要如何操作?
不可以。g11开始修复了旧版本对std::visit的检查漏洞,严格执行C标准要求:std::visit传入的访问器必须对variant的所有可选类型返回完全相同的类型。你当前代码里的lambda会根据传入的类型返回const Cat&或const Dog&,属于不同类型,本身就不符合标准规范,旧版本是未做严格检查才允许编译通过,属于实现缺陷,g++11之后就算加-fpermissive也不会跳过该检查。
如果要保留原有功能不需要改外层调用,可以调整getAnimal的实现,把公共逻辑放到visit内部完成,例如:
const std::string& getSound() const { return std::visit([](const auto& animal) -> const std::string& { return animal.getSound(); }, v); }
2. 为什么添加-fpermissive参数可以让旧版本g++编译通过该代码?
-fpermissive的作用是降低编译器语法检查严格度,将部分不符合C标准的错误降级为警告,用来兼容非标准写法。
旧版g的std::visit实现没有严格做返回类型一致性的静态检查,加上-fpermissive之后编译器直接忽略了返回类型不一致的问题,所以能编译通过,这种行为本身不符合C++标准。
3. 这段示例代码是否可以使用MSVC编译?
不可以。MSVC的std::visit实现一直严格执行返回类型一致性检查,不存在旧版gcc的实现漏洞,就算加兼容参数也不会允许编译这种不符合标准的代码。
4. 这段示例代码是否可以使用clang编译?
不可以。clang的std::visit实现同样严格遵循C++标准要求,会直接报错返回类型不一致,和你测试的结果一致。
注:如果你坚持要按当前
getAnimal返回auto&的写法实现,本质需求是运行时多态,std::variant本身不支持直接向外暴露不同类型的引用,要么改用继承多态实现,要么把逻辑封装到visit内部完成。
内容的提问来源于stack exchange,提问作者Георгий Гуминов

