模板模板参数的SFINAE机制
This question 最近被提出:是否有可能设计一个特征(traits)来判断一个类是否拥有给定的 模板 非静态成员函数(注:我认为它也可以扩展到模板静态成员函数)。
当该函数不是模板时,有众所周知的实现;而当它是模板时,常见的解决方案是通过查找一个给定的实例。
我觉得它并不令人满意;比如你对一个 int 实例进行检查时,若成员函数只接受浮点数参数,该特质将返回false,尽管你希望该特质也为true。
显然的解决办法是使用一个符合你要求的“测试”类型,但它并不具备通用性,我也想避免在模板模板参数上做这种“测试”类型的SFINAE,其中该参数表示模板函数:
#include <cassert>
#include <concepts>
#include <type_traits>
struct ArbitraryClass {
template <std::floating_point T>
void Serialize(T&) {}
};
struct OtherClass {};
template <typename... T>
struct void_T {};
template <typename Class>
struct wrapper {
template <typename T,
typename U = decltype(std::declval<Class>().template Serialize<T>(
std::declval<T&>()))>
struct type_impl {};
template <typename T>
struct type : type_impl<T> {};
};
template <typename T, template <typename U> typename = void_T>
struct HasSerialize : std::false_type {};
template <typename T>
struct HasSerialize<T, wrapper<T>::template type> : std::true_type {};
int main() {
static_assert(HasSerialize<ArbitraryClass>::value, "Has no Serialize");
static_assert(not HasSerialize<OtherClass>::value, "Has no Serialize");
static_assert(not HasSerialize<int>::value, "Has no Serialize");
}
但它在多种方式上失败了 LIVE
首个断言在所有测试编译器上都意外失败。
clang也拒绝其他那些(断言不会触发,它们只是不能编译通过):
<source>:17:68: error: no member named 'Serialize' in 'OtherClass'
17 | typename U = decltype(std::declval<Class>().template Serialize<T>(
| ~~~~~~~~~~~~~~~~~~~~~ ^
<source>:27:24: note: in instantiation of template class 'wrapper<OtherClass>' requested here
27 | struct HasSerialize<T, wrapper<T>::template type> : std::true_type {};
| ^
<source>:31:23: note: during template argument deduction for class template partial specialization 'HasSerialize<T, wrapper<T>::template type>' [with T = OtherClass]
31 | static_assert(not HasSerialize<OtherClass>::value, "Has no Serialize");
| ^
<source>:31:23: note: in instantiation of template class 'HasSerialize<OtherClass>' requested here
<source>:17:58: error: member reference base type 'int' is not a structure or union
17 | typename U = decltype(std::declval<Class>().template Serialize<T>(
| ~~~~~~~~~~~~~~~~~~~~~^ ~~~~~~~~~
<source>:27:24: note: in instantiation of template class 'wrapper<int>' requested here
27 | struct HasSerialize<T, wrapper<T>::template type> : std::true_type {};
| ^
<source>:32:23: note: during template argument deduction for class template partial specialization 'HasSerialize<T, wrapper<T>::template type>' [with T = int]
32 | static_assert(not HasSerialize<int>::value, "Has no Serialize");
| ^
<source>:32:23: note: in instantiation of template class 'HasSerialize<int>' requested here
除了clang与其他编译器之间的差异外,我不明白为什么第一个断言会触发。
我的推理如下:HasSerialize<ArbitraryClass> 可以用第二个模板参数为 void_T 或 wrapper<T>::template type 来实例化,其中后者更具专门化。因此,我本来会期望 HasSerialize<ArbitraryClass> 会从 std::true_type 继承。
另一方面,对于另一类,wrapper<T>::template type 无效,因为它的第二个模板参数无法被替换。
我几乎可以肯定我的推理是错误的,但我想知道原因,以及是否有办法实现这种SFINAE,若没有,为什么也是如此。
解决方案
我的推理如下:
HasSerialize<ArbitraryClass>可以用第二个模板参数为void_T或wrapper<T>::template type来实例化,这两个选项中后者更具专门化。因此,我本来会期望HasSerialize<ArbitraryClass>继承自std::true_type。
情况并非如此,
HasSerialize<ArbitraryClass> 使用默认参数的主模板,因此是 HasSerialize<ArbitraryClass, void_T>。替换后,专门化没有把 void_T 作为第二个参数,所以不会被选中。
HasSerialize<T>::value 对任何类型 T 都会是false。
[..] 如果有办法实现这种SFINAE
你不能检查所有类型。 顺便说一句,通常你只想检查某些特定类型(可能是模板类型),因为你不会对所有类型都调用该函数。