【问题标题】:Return type deduction for methods of nested classes嵌套类方法的返回类型推导
【发布时间】:2021-12-24 16:42:00
【问题描述】:

我遇到了与嵌套类方法的返回类型推导有关的问题。经过一番挖掘,我将问题缩小到简化版本如下:

#include <type_traits>

struct Alive {};

template<typename T>
auto is_alive_impl(...) -> void;

template<typename T>
auto is_alive_impl(int) -> decltype(std::declval<T>().operator()());

template<typename T>
struct is_alive : std::is_same<decltype(is_alive_impl<T>(0)), Alive> {};

struct Box {
    struct Cat {
        auto 
        operator()() {
            return Alive{};
        }
    };

    static constexpr auto value = is_alive<Cat>::value; // (1)
};

int main() {
    static_assert(is_alive<Box::Cat>::value); // (2)
}

Compiler Explorer link

struct is_alive 检查一个类是否有 operator() 使用函数重载技巧返回一个 Alive 实例。

如果没有第 (1) 行,则 (2) 处的 static_assert 返回 true 并且代码按预期编译。

但是,我不明白为什么添加第 (1) 行使第 (2) 行失败。好像我在盒子里看了看,不知何故杀死了猫。

所以这是我的问题:

  1. 为什么在第 (1) 行,value 是假的?
  2. 为什么添加第 (1) 行使第 (2) 行失败?

如果您可以参考标准中规定该行为的部分,那就太好了。

【问题讨论】:

  • 不知何故将Cat::operator() 的返回类型更改为Alive 有效,但不知道原因。我的猜测是decltype(is_alive_impl&lt;T&gt;(0) 无法推断出Alive,所以他们选择了返回void 的另一个重载。
  • 第 (1) 行使 (2) 失败,因为模板的不同实例化点在 ODR 方面必须一致(这样,编译器可以缓存实例化)。当存在分歧时,代码格式错误;无需诊断。至于分歧本身是否有效……高于我的工资等级。
  • @StoryTeller-UnslanderMonica 所以编译器在点 (1) 实例化 is_alive 并缓存结果以在点 (2) 重用?但是为什么在第 (1) 点,值是假的?我认为 Cat 类的定义是完整的,所以 value 应该是真的?谢谢。
  • @LanguageLawyer 我尝试了问题 (godbolt.org/z/GGc71Y7hs) 中的示例,编译器彼此不同意。所以这是解析问题的阶段?

标签: c++ templates language-lawyer


【解决方案1】:

您或多或少地在 SFINAE 上下文中运行所有代码,因此您看不到任何问题。如果您只是尝试通过

来实例化您的类
int main() { Box::Cat bc; }

我们看到了所有的错误信息。

会发生什么:

如果您想在类中定义(而不仅仅是声明!)一个需要推断您的operator() 的返回类型的变量,则该语句将失败,因为在定义类之前无法推断出auto。 (在带有右括号的类定义的末尾。) 这也让std::declval&lt;T&gt;() 失败,因为auto value = is_alive&lt;Cat&gt;::value 将不起作用。由于我们在 SFINAE 上下文中并且替换静默失败,我们在默认模板 auto is_alive_impl(...) -&gt; void; 中运行。嗯 :-)

如果我们增加一点间接性并将猫放在一个更小的盒子里,我们可以运行代码:

struct LittleBox
{
    struct Cat {
        auto
        operator()()  {
            return Alive{};
        }
    };  

};

struct Box: public LittleBox {

    static constexpr  auto value = is_alive<Cat>::value; // (1)
};

好的,我们可以更简单地定义operator()() 的返回类型并且不要在这里使用auto

修复或解决方法似乎适用于 gcc/msvc/clang:explore

【讨论】:

  • 当我执行Box::Cat c; 时,它编译得很好。并且使用 Box b; ,根据调试器,在这两种情况下对象都是空的。使用最新的 MSVC。
  • @lakeweb:是的,clang 也可以,但 gcc 失败了。似乎很难在标准的边缘。当“语言律师”指出缺陷报告时,我们应该将该问题视为语言缺陷 :-) 修复或解决方法似乎适用于所有平台(msvc/clang/gcc)。
  • @Klaus 你的LittleBox 解决方案很有趣,它实际上解决了我的问题。关于第二个建议(不要使用自动)在我的特定情况下是不可行的。因为我正在使用模板繁重的库,所以写下所有返回类型并不是很实用。但是我不清楚一件事,为什么我的示例中的第 (1) 行返回 false?我认为 Cat 类的定义在那个查询点是完整的?
  • @mibu 我看到两个表达式都返回 true 或都返回 false。如前所述,我认为这是因为在您的 SFINAE 表达式期间无法实例化该对象。在哪一点上可以推断出 auto 值似乎是标准的一个开放点,并且实现处理它的方式不同。由于我们不能说什么是正确的,我们不能给编译器供应商一个错误报告。这是本周我们第二次发现这样的语言“特征”。照原样接受。有时语言缺陷报告会保留数十年... :-)
猜你喜欢
  • 2011-01-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-11
  • 1970-01-01
  • 2010-10-18
  • 2020-04-22
相关资源
最近更新 更多