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 进行编译时,我收到了含义非常接近的消息。 我的问题是:

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.

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

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