这种在if constexpr内使用static_assert的用法是否符合规范?

7

昨天我看了一些关于在if constexprelse语句中使用static_assert(false, "Some message")答案,根据标准来说这被认为是不合法的,尽管某些编译器(包括MSVC2017)会接受它。QT也会将其标记为错误。

我的问题是,下面的代码是否符合标准? (我倾向于认为是,但我想得到确认。)

template <typename TypeOfValue>
static void PushValue(duk_context* ctx, TypeOfValue value) {
    // Push value onto duktape stack
    if constexpr (std::is_same<TypeOfValue, int>::value) {
        // Push int
        duk_push_int(ctx, value);
    } else if constexpr (std::is_same<TypeOfValue, uint32_t>::value) {
        // Push uint
        duk_push_uint(ctx, value);
    } else {
        // Unsupported type
        static_assert(bool_value<false, TypeOfValue>(), "Unsupported type");
    }    
}

template <bool value, typename T>
static constexpr bool bool_value() {return value;}        

编辑:

从我得到的评论中看来,bool_value 应该像这样定义:

template<bool value, typename T>
struct bool_value { 
    static constexpr bool value = value; 
};

使用模式

// Unsupported type
static_assert(bool_value<false, TypeOfValue>::value, "Unsupported type");

只有当bool_value可以被特化为一个版本,该版本返回表达式bool_value<false, TypeOfValue>::valuetrue时,它才是良构的。


“cppreference reads”(https://en.cppreference.com/w/cpp/language/if#Constexpr_If):“被丢弃的语句不能对于每个可能的特化都是非法的”。但是`bool_value<false, TypeOfValue>()对于每个可能的特化都是false`。 - Evg
2
我记得在C++会议演示之后(抱歉,我不记得是哪一个),有关这种代码的讨论。结果是这样的:实际上,这总是有效的,但从理论上讲,这仍然是不规范的,因为 bool_value<false, TypeOfValue>::value 总是 false,除非你实际上有一个返回 true 的特化(例如 std::is_void_t<TypeOfValue>)。考虑将 [tag:language-lawyer] 标签添加到您的问题中。 - Evg
如果我理解你的意图,我会将检查提升到模板的直接上下文中。如果需要的话,我可以创建一个godbolt。 - Taekahn
@Taekahn 目标是,如果我使用错误的参数与模板一起使用,那么就会得到编译器错误。我知道它可以通过其他方式解决(例如,分别重载函数),但我正在寻找对这种特定方法的评判。 - bjaastad_e
@ Elling,我会直接使用cppreference中的示例。它看起来更加简洁。 - Taekahn
3个回答

5
你的两个尝试(使用函数和结构体)都是良好的形式。 其他答案提到了[temp.res]/8,但我不同意它的解释方式。
引用块: 一个模板的有效性可以在任何实例化之前进行检查。……如果: - 不能为模板或constexpr if语句中的子语句生成有效的特化,并且未实例化该模板,或者…… 则程序无效,无需诊断。
你编写的函数和结构体都可以专门用于true。我认为仅仅可能进行专业化就足够了,您实际上不需要添加虚拟true以使代码良好形式化。
根据我的理解(并希望根据常识),标准的这一部分的目的是允许编译器在早期(第一次看到时)检查模板和if constexpr分支的有效性,并拒绝那些不可能被实例化的分支。
你的模板中每个分支都可能被实例化,因为 `bool_value()` 可以稍后进行特化。我怀疑一个理智的编译器不会因为 `bool_value()` 还没有被特化而拒绝你的代码。

1
您链接的问题的接受答案解释了为什么它是不合法的。请注意标准temp.res-8中的引用,其中说(重点在于)

在任何实例化之前,可以检查模板的有效性。如果:

  • 无法为模板或constexpr if语句中的子语句生成有效的特化,并且未实例化该模板,或者...

[...]

否则,对于可以生成有效特化的模板,不应发出诊断。 [注:如果实例化模板,则将根据本文档中的其他规则诊断错误。 这些错误何时被诊断是实现问题的质量。 — 结束注释]

在cppreference 页面 关于 if 的内容中,你可以看到以下的解决方法(重点是我的):

对于这种 catch-all 语句的常见解决方法是一个类型相关的表达式,它总是为 false:

template<class T> struct dependent_false : std::false_type {};
template <typename T>
void f() {
     if constexpr (std::is_arithmetic_v<T>)
         // ...
     else
       static_assert(dependent_false<T>::value, "必须是算术类型"); // ok
}
正如评论Evg所指出的那样:

从理论上讲,这仍然是不合法的,因为[...]总是false,除非你实际上有一个返回 true 的特化。

所以你可以将该特化添加到之前的代码片段中。
命名空间 {
    // 不要释放。
    结构体 aggressive_unicorn_type;
}
template<> 结构体 dependent_false<aggressive_unicorn_type> : std::true_type {};

这是否必要完全遵守标准文件,取决于对"可以"的解释。

我想知道这是否真的很重要。 OP正在询问代码是否格式良好,但实际上他们正在尝试生成一段有诊断(这就是static_assert(false)意味着的内容,请参见dcl.pre)的不良代码。

正如提问者已经注意到的那样,当传递了“错误”类型的参数时,还有其他方法来强制编译器出错(考虑概念,即即将到来的标准),并且,也许,如果我们想要在结合使用if constexprstatic_assert时使用一个不总是为假的类型相关表达式,最好的选择可能是一个类型相关表达式。

#include <type_traits>
void f(int) {}; void g(unsigned) {};
template< class T> constexpr inline bool is_supported = std::is_same_v<T, int> || std::is_same_v<T, unsigned> || std::is_same_v<T, char>;
template<class T> void use(T value) { static_assert(is_supported<T>, "不支持的类型"); if constexpr (std::is_same<T, int>::value) { f(value); } else if constexpr (std::is_same_v<T, unsigned>) { g(value); } else { static_assert( !is_supported<T>, "尚未实现"); } }

1
我将作为答案添加自己的最终想法,因为似乎从其他来源得不到足够原则性的答案。
在阅读了几个其他答案后,我的结论是:
1.编译器允许根据标准提前评估static_assert(false, "...")。如果这样做,它会生成一个永远无法编译的程序。 2.永远无法编译的程序是不良形式的。
因此,问题在于编译器允许在if constexpr内部早期评估静态断言。
强制它推迟评估直到实例化时间,通过引入一个人工依赖于模板参数的false,对我来说似乎有点牵强。但我接受可能有其他考虑,这可能使它成为正确的语言选择。(这就是我希望专家回答能够阐明的问题。)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接