【问题标题】:SFINAE with variadic templates带有可变参数模板的 SFINAE
【发布时间】:2015-12-14 16:10:10
【问题描述】:

我对模板编程有点陌生,所以这可能是一个愚蠢的问题。我正在尝试使用可变参数模板来检查一个类是否有一个成员(称为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 仅出现在模板(或专业化)的声明中,而不是定义中。所以tester 没有启用 SFINAE。 SFINAE 很棘手,这使它保持在一个有限的位置(因此意外错误将产生错误而不是静默替换失败)并使编译器编写者稍微容易一些。
  • 立即上下文中的替换失败不是大象。类模板定义不在直接上下文中。
  • 嗯.. 我需要阅读 SFINAE。有没有办法使用可变参数模板来做我想做的事情?我确实有一种方法可以使用带有可变参数函数的函数重载。

标签: c++ templates c++11


【解决方案1】:

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);

【讨论】:

  • 非常感谢您的回答。如果我将专业类替换为struct has_member&lt;T&gt; : std::true_type,void_t&lt;decltype(T::member)&gt;,我试图查看该解决方案是否仍然有效。我最终得到了类似的错误。继承在直接上下文中不算数吗?
  • @user2308211 正确。此外,在成功的情况下,您将从void 继承,无论如何这是不允许的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-01-15
  • 1970-01-01
  • 1970-01-01
  • 2014-09-08
  • 1970-01-01
  • 2012-01-20
  • 2013-04-17
相关资源
最近更新 更多