具有可变模板的SFINAE

SFINAE with variadic templates

本文关键字:SFINAE      更新时间:2023-10-16

我对模板编程有点陌生,所以这可能是一个愚蠢的问题。我试图使用可变模板来检查类是否有成员(称为member)。为此,我编写了这个类has_member

#include <iostream>
using namespace std;
class ClassWithMember
{
    public:
    int member;
};
class ClassWithoutMember
{
};
template <typename T>
class has_member
{
    template <typename... C>
    class tester: public std::false_type
    {
    };
    template <typename First>
    class tester<First>: public std::true_type
    {
        void tester_fn(decltype(First::member));
    };    
public:
    enum { value = tester<T>::value };
};
template<typename T1>
void my_function(const std::enable_if_t<has_member<T1>::value, T1> &obj)
{
    cout<<"Function for classes with member"<<endl;
}
template<typename T1>
void my_function(const std::enable_if_t<!has_member<T1>::value, T1> &obj)
{
    cout<<"Function for classes without member"<<endl;
}
int main()
{
    ClassWithMember objWithMember;
    ClassWithoutMember objWithoutMember;
    my_function<ClassWithMember> (objWithMember);
    my_function<ClassWithoutMember> (objWithoutMember);
}

我期望通过SFINAE,用不带成员的类替换特化模板会无声地失败,并回落到通用模板。但是我得到了错误:

trial.cpp: In instantiation of ‘class has_member<ClassWithoutMember>::tester<ClassWithoutMember>’:
trial.cpp:28:10:   required from ‘class has_member<ClassWithoutMember>’
trial.cpp:38:41:   required by substitution of ‘template<class T1> void my_function(std::enable_if_t<(! has_member<T1>::value), T1>&) [with T1 = ClassWithoutMember]’
trial.cpp:49:54:   required from here
trial.cpp:24:14: error: ‘member’ is not a member of ‘ClassWithoutMember’
     void tester_fn(decltype(First::member));

SFINAE仅适用于直接上下文替换。之外的替换失败是错误。这就是你遇到的问题:

has_member<ClassWithoutMember>::value // error

这是因为替换失败不是发生在has_membertester的声明中,而是发生在定义中。那太晚了。你得把它推得更早。您可以使用void_t将其推入has_member的专门化:

template <typename... T>
struct make_void { using type = void; };
template <typename... T>
using void_t = typename make_void<T...>::type;
template <typename T, typename = void>
struct has_member : std::false_type { };
template <typename T>
struct has_member<T, void_t<decltype(T::member)>> : std::true_type { };

现在,如果没有T::member,在尝试选择has_member的正确专门化时,替换失败将发生在替换的直接上下文中。替换失败是而不是错误,特定的专门化将被丢弃,我们最终得到false_type


作为旁注,您使用enable_if_t的方式可以防止模板演绎。你应该这样写:

template <typename T1,
          std::enable_if_t<has_member<T1>::value>* = nullptr>
void my_function(const T1& obj) { ... }
template <typename T1,
          std::enable_if_t<!has_member<T1>::value>* = nullptr>
void my_function(const T1& obj) { ... }

你可以这样写:

my_function(objWithMember);
my_function(objWithoutMember);