问题描述
#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;
}
它的有趣之处在于它使用从 g++-8 到 g++-10 的编译器进行编译。 (使用标志 -std=c++17
和 -fpermissive
)并在使用 g++-11 时失败。如果它编译,它会按预期工作 - 打印“喵”。和“树皮”。在单独的行上。错误消息看起来像这样 (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 编译时,我收到的消息含义非常接近。 我的问题是:
- 是否可以使用
g++-11
编译示例代码?如果答案是“是”,那怎么办? - 为什么添加
-fpermissive
会使 g++ 编译器在这种情况下工作? - 是否可以使用 MSVC 编译示例代码?
- 是否可以使用 clang 进行编译? (我试过了。)
附言我知道继承和模板。我只是有一个兴趣,是否可以按照我在示例中的方式进行操作。
解决方法
gcc-8 不允许这样做,并且在您尝试使用它时会产生损坏的代码。
先暂且搁置 std::visit()
和 std::variant<>
的官方定义,从纯语言的角度来看,这直观地必须如此。
为了证明这一点,让我们问自己一个问题:“getAnimal()
的返回类型是什么?”。这毕竟要在编译时确定。
返回 auto
的函数的返回类型完全由其参数决定。在这种情况下,只有 House
this
,没有别的。所以变体的当前状态不能影响返回的类型。它可能是什么?也许某种推断依赖variant<>
?但是这样你就不能直接调用 getSound()
了,所以不可能。
让我们不要再疑惑了,直接用 typeid()
自己检查一下:
using T = decltype(std::declval<House>().getAnimal());
std::cout << typeid(T).name() << "\n";
// ...
result:
3Cat
看起来我们只能让猫离开这个功能!我们可以通过稍微更改您的代码来确认这一点:
class Cat {
public:
const std::string& getSound() const {
std::cout << "I am cat\n";
return sound;
};
private:
std::string sound = "Meow.";
};
class Dog {
public:
const std::string& getSound() const {
std::cout << "I am dog\n";
return sound;
};
private:
std::string sound = "Bark.";
};
//...
result:
I am cat
Meow.
I am cat <--------- !!!!!!
Bark.
它在您的示例中“有效”的事实是由 Cat
和 Dog
碰巧具有相同的内存布局引起的一个小奇迹。
它仍然是未定义的行为,即使它“有效”。
,至于 std::visit,访问者是一个 Callable,返回相同类型 R 以及来自变体的任意类型组合。您的访问者返回不同的类型。 GCC 的 libstdc++ 直到 GCC11 才检查这个规则。稍后更新,添加了诊断: libstdc++: Fix visitor return type diagnostics [PR97449]。