微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

GNU C++ stdlib std::variant 实现的变化

如何解决GNU C++ stdlib 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;
}

它的有趣之处在于它使用从 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.

它在您的示例中“有效”的事实是由 CatDog 碰巧具有相同的内存布局引起的一个小奇迹。

它仍然是未定义的行为,即使它“有效”。

,

至于 std::visit,访问者是一个 Callable,返回相同类型 R 以及来自变体的任意类型组合。您的访问者返回不同的类型。 GCC 的 libstdc++ 直到 GCC11 才检查这个规则。稍后更新,添加了诊断: libstdc++: Fix visitor return type diagnostics [PR97449]

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。