是否可以通过 C++20 中的非类型模板参数使用特征来确定对象是否属于类类型?

nok*_*okz 8 c++ language-lawyer compiler-bug c++20 non-type-template-parameter

考虑这段代码:

#include <type_traits>

template <typename T>
struct wrapper {
    T& ref;
    constexpr wrapper(T& ref) : ref(ref) {}
};

// Trait that checks whether a type is of the form `wrapper<T>`
template <typename T>
struct is_wrapper_type : std::false_type {};

template <typename T>
struct is_wrapper_type<wrapper<T>> : std::true_type {};

// Trait that checks whether an object is of the type `wrapper<T>`
template <auto& Value>
struct is_wrapper_object;

template <auto& Value>
    requires (!is_wrapper_type<std::decay_t<decltype(Value)>>::value)
struct is_wrapper_object<Value> : std::false_type {};

template <auto& Value>
    requires is_wrapper_type<std::decay_t<decltype(Value)>>::value
struct is_wrapper_object<Value> : std::true_type {};

int main() {
    static constexpr int v = 42;
    static_assert(!is_wrapper_object<v>::value);
    static constexpr wrapper w {v};
    static_assert(is_wrapper_object<w>::value);
}
Run Code Online (Sandbox Code Playgroud)

上面的代码在 Clang 中编译失败,并出现如下错误,但在 GCC 中编译成功

<source>:30:20: error: implicit instantiation of undefined template 'is_wrapper_object<v>'
   30 |     static_assert(!is_wrapper_object<v>::value);
      |                    ^
<source>:18:8: note: template is declared here
   18 | struct is_wrapper_object;
      |        ^
<source>:32:19: error: implicit instantiation of undefined template 'is_wrapper_object<w>'
   32 |     static_assert(is_wrapper_object<w>::value);
      |                   ^
<source>:18:8: note: template is declared here
   18 | struct is_wrapper_object;
      |        ^
Run Code Online (Sandbox Code Playgroud)

这里哪个编译器是正确的?

我相信 GCC 在这里应该是正确的,因为上面的代码在纸面上应该是合法的,但是,我不确定情况是否确实如此。

此外,如果我将特征的定义更改is_wrapper_object为:

<source>:30:20: error: implicit instantiation of undefined template 'is_wrapper_object<v>'
   30 |     static_assert(!is_wrapper_object<v>::value);
      |                    ^
<source>:18:8: note: template is declared here
   18 | struct is_wrapper_object;
      |        ^
<source>:32:19: error: implicit instantiation of undefined template 'is_wrapper_object<w>'
   32 |     static_assert(is_wrapper_object<w>::value);
      |                   ^
<source>:18:8: note: template is declared here
   18 | struct is_wrapper_object;
      |        ^
Run Code Online (Sandbox Code Playgroud)

也可以在 Clang 上编译,尽管输出错误

Jan*_*tke 3

海湾合作委员会是正确的。

请注意,只需将您替换auto&为即可const auto&使代码针对两者进行编译。无论问题是什么,它都与部分特化中的占位符类型说明符有关。

注意:

如果模板参数T的类型包含占位符类型或推导类类型的占位符 ([dcl.type.class.deduct]),则参数的类型是为发明声明中的变量推导的类型x

T x = E ;
Run Code Online (Sandbox Code Playgroud)

- [temp.arg.nontype] p1

换句话说,auto&模板中的参数应该是const int&const wrapper&之后这样的推导。用户不需要const自己提供。

我无法找到相关的 LLVM 错误报告,因此我提交了一份:LLVM Bug 77189

最小可重复示例

参见https://godbolt.org/z/rhKsWKdPz

#include <type_traits>

template <auto& Value, int>
struct test : std::false_type {};

template <auto& Value>
struct test<Value, 0> : std::true_type {};

int main() {
    static constexpr int v = 42;
    static_assert(test<v, 0>::value); // fails for clang
}
Run Code Online (Sandbox Code Playgroud)

由此可见,clang完全无法Value在 的部分特化范围内进行推导test