GNU C++ stdlib std::variant 实现的变化
Changes in GNU C++ stdlib std::variant implementation
我有以下示例 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;
}
有趣的是它可以使用从 g++-8 到 g++-10 的编译器进行编译。 (使用标志 -std=c++17
和 -fpermissive
)并在使用 g++-11 时失败。如果它编译,它会按预期工作 - 在不同的行上打印 '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 进行编译时,我收到了含义非常接近的消息。
我的问题是:
- 是否可以使用
g++-11
编译示例代码?如果答案是'Yes.',那怎么办?
- 为什么添加
-fpermissive
可以使 g++ 编译器在这种情况下工作?
- 是否可以使用 MSVC 编译示例代码?
- 是否可以使用clang编译? (我试过了。)
P.S。我知道继承和模板。我只是有兴趣,是否可以像我在示例中那样做。
至于std::visit,访问者是一个 Callable,它返回相同类型 R 以及变体类型的任意组合。您的访客 returns 不同类型。 GCC 的 libstdc++ 直到 GCC11 才检查这个规则。这是后来更新的,添加了诊断:
libstdc++: Fix visitor return type diagnostics [PR97449].
gcc-8 在允许此方面存在问题,并在您尝试使用它时生成错误代码。
暂时搁置 std::visit()
和 std::variant<>
的官方定义,从纯语言的角度来看,直觉上 必须 是这种情况。
为了证明这一点,让我们问自己一个问题:“getAnimal()
的 return 类型是什么?”。这毕竟要在编译时确定。
returns auto
的函数 return 类型完全由其参数决定。在这种情况下,只有 House
this
,没有别的。所以变体的当前状态不能影响returned类型。它可能是什么?也许某种推断依赖 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::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;
}
有趣的是它可以使用从 g++-8 到 g++-10 的编译器进行编译。 (使用标志 -std=c++17
和 -fpermissive
)并在使用 g++-11 时失败。如果它编译,它会按预期工作 - 在不同的行上打印 '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 进行编译时,我收到了含义非常接近的消息。 我的问题是:
- 是否可以使用
g++-11
编译示例代码?如果答案是'Yes.',那怎么办? - 为什么添加
-fpermissive
可以使 g++ 编译器在这种情况下工作? - 是否可以使用 MSVC 编译示例代码?
- 是否可以使用clang编译? (我试过了。)
P.S。我知道继承和模板。我只是有兴趣,是否可以像我在示例中那样做。
至于std::visit,访问者是一个 Callable,它返回相同类型 R 以及变体类型的任意组合。您的访客 returns 不同类型。 GCC 的 libstdc++ 直到 GCC11 才检查这个规则。这是后来更新的,添加了诊断: libstdc++: Fix visitor return type diagnostics [PR97449].
gcc-8 在允许此方面存在问题,并在您尝试使用它时生成错误代码。
暂时搁置 std::visit()
和 std::variant<>
的官方定义,从纯语言的角度来看,直觉上 必须 是这种情况。
为了证明这一点,让我们问自己一个问题:“getAnimal()
的 return 类型是什么?”。这毕竟要在编译时确定。
returns auto
的函数 return 类型完全由其参数决定。在这种情况下,只有 House
this
,没有别的。所以变体的当前状态不能影响returned类型。它可能是什么?也许某种推断依赖 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
碰巧具有相同的内存布局所造成的一个小奇迹。
它仍然是未定义的行为,即使它“有效”。