Consider this code:
#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);
}
The above fails to compile in Clang with the errors shown below but compiles successfully in 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;
| ^
Which compiler is correct here?
I believe GCC should be correct here as the above code should be legal on paper, however, I am not sure whether that is really the case.
Moreover, if I change the definition of the trait is_wrapper_object to this:
template <auto& Value>
struct is_wrapper_object : std::false_type {};
template <auto& Value>
requires is_wrapper_type<std::decay_t<decltype(Value)>>::value
struct is_wrapper_object<Value> : std::true_type {};
GCC is correct.
Note that simply replacing your
auto&withconst auto&makes the code compile for both. Whatever the issue is, it has something to do with placeholder type specifiers in partial specializations.Note that:
- [temp.arg.nontype] p1
In other words,
auto&in a template parameter should beconst int&andconst wrapper&after such deduction. It should not be necessary for the user to provideconstthemselves.I was unable to find a relevant LLVM bug report, so I have submitted one: LLVM Bug 77189.
Minimal Reproducible Example
See https://godbolt.org/z/rhKsWKdPz
From this, we can tell that clang is completely unable to deduce
Valuewithin the partial specialization oftest.