代码之家  ›  专栏  ›  技术社区  ›  Steven Stallion

为什么无效()是合法的,而无效{}不是?[副本]

  •  4
  • Steven Stallion  · 技术社区  · 2 年前

    今天晚上在制作模板时,我发现了一些令人惊讶的事情。有问题的模板使用了 std::invoke 在满足特定条件集时调用函数,否则在记录错误消息后返回默认构造的返回类型。这一直运行良好,直到我需要将模板应用于返回的函数 void 。在与比我更了解情况的人交谈后,他指出,使用括号而不是大括号来构造返回类型将与void兼容。

    果不其然,这适用于我所能使用的任何编译器,但它在更深层次上困扰着我。在下面的代码中 R() 看起来是默认构造的,但我怀疑这确实是函数样式转换的异常情况(例如。 void() ),这解释了为什么使用 R{} (例如。 void{} )被大多数(但不是所有)编译器认为是非法的。

    是否有语言律师可以验证上述内容,并就实现以下模板的最惯用方法提供建议,而无需使用概念创建两个版本?(我目前的目标是C++20)。

    template<typename F, typename... Args,
             typename R = std::invoke_result_t<F, Args...>>
    auto call(F&& f, Args&&... args)
    {
        if (some_runtime_condition) [[unlikely]] {
            // log an error message
            return R(); // R{} is invalid for functions returning void
        }
    
        return std::invoke(std::forward<F>(f), std::forward<Args>(args...));
    }
    

    https://godbolt.org/z/fos4T6Wfj

    TIA!

    1 回复  |  直到 2 年前
        1
  •  6
  •   NathanOliver    2 年前

    所以这里同时发生了两件事。首先,在C++20之前,标准中存在一个缺陷,其中只有表达式 void() 是允许的,这是从C++03结转的,在C++11到C++17中从未更新过,也允许 void{} 做同样的事情。

    由于C++20,标准的措辞是 [expr.type.conv]/2 :

    如果初始值设定项是带括号的单个表达式,则类型转换表达式等效于相应的强制转换表达式。 否则,如果类型为cv void,初始值设定项为()或{}(在包扩展后,如果有),则表达式为void类型的prvalue,不执行初始化 。否则,表达式是指定类型的prvalue,其结果对象是使用初始值设定项直接初始化的。如果初始值设定项是带括号的可选表达式列表,则指定的类型不应为数组类型。

    重点矿井

    这意味着你的代码应该编译,而且它确实使用 GCC and MSVC, just not clang