【问题标题】:Changes in GNU C++ stdlib std::variant implementationGNU C++ stdlib std::variant 实现的变化
【发布时间】:2021-10-03 11:32:13
【问题描述】:

我有以下使用 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 进行编译? (我试过了。)

附:我知道继承和模板。我只是有兴趣,是否可以按照我在示例中的方式进行操作。

【问题讨论】:

  • decltype(std::declval&lt;House&gt;().getAnimal()) 应该是什么?

标签: c++ visual-c++ g++ clang c++17


【解决方案1】:

gcc-8 不允许这样做,并且在您尝试使用它时会产生损坏的代码。

暂且搁置std::visit()std::variant&lt;&gt; 的官方定义,从纯语言的角度来看,直观地必须如此。

为了证明这一点,让我们问自己一个问题:“getAnimal() 的返回类型是什么?”。毕竟这必须在编译时确定。

返回auto 的函数的返回类型完全由其参数决定。在这种情况下,只有House this,没有别的。所以变体的当前状态不能影响返回的类型。它可能是什么?也许某种推断依赖variant&lt;&gt;?但是那样你就不能直接在上面调用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 碰巧具有相同的内存布局引起的一个小奇迹。

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 很有趣,这个问题在g++ 中这么久都没有解决。
    猜你喜欢
    • 2012-08-21
    • 2019-10-11
    • 1970-01-01
    • 2017-03-18
    • 1970-01-01
    • 2023-03-19
    • 2012-11-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多