【问题标题】:Can I use SFINAE to detect template class member functions?我可以使用 SFINAE 检测模板类成员函数吗?
【发布时间】:2013-08-06 16:53:03
【问题描述】:

我多次成功使用 SFINAE。检测一个类是否提供函数不是问题。我目前的问题似乎与his problem相反!而不是also 检测派生方法,我更愿意检测only 类的方法。似乎与方法是模板有关。

是否可以检测类模板方法?我试图用一种不应该伤害的类型来实例化模板,但没有运气。

struct A { template<class T> void Func( T ) {}; };
struct B :A {};

template< class T >
struct CheckForFunc
{
    typedef char(&YesType)[1];
    typedef char(&NoType)[2];
    template< class U, void (U::*)( int ) > struct Sfinae;

    template< class T2 > static YesType Test( Sfinae<T2,&T2::Func>* );
    template< class T2 > static NoType  Test( ... );
    static const bool value = sizeof(Test<T>(0))==sizeof(YesType);
};

int main(int argc, char* argv[])
{
    // gives "1"
    std::cout << "Value A=" << CheckForFunc< A >::value << std::endl;
    // doesn't compile!
    std::cout << "Value B=" << CheckForFunc< B >::value << std::endl;
    return 0;
}

错误信息:

error: ‘&A::Func’ is not a valid template argument for type ‘void (B::*)(int)’ because it is of type ‘void (A::*)(int)’

请注意,此 SFINAE 非常适用于模板方法,但不适用于派生!不好的是它不仅检测到错误,而且编译失败。

如何在不使用“样本”类型(此处为 int)的情况下编写 SFINAE 测试?

编辑:抱歉,仅限 C++03! LLVM 很好用,还有 VS2008,只是 GCC 和 QNX 不行(我明天得看看版本)。

Edit2:不知道 Coliru!很酷,这里是error

【问题讨论】:

  • 为什么不直接使用Test(void (T2::*)(int) = nullptr)sizeof(Test&lt;T&gt;())
  • Your code compiles, using clang++。我觉得这很奇怪!我认为这是clang中的一个错误!
  • 使用默认参数的有趣想法。 nullptr 是 C++11 吗?
  • 实际的问题是Sfinae&lt;T2,&amp;T2::Func&gt;* 是否会导致T2 == B 的替换失败(后一种情况:代码无法编译)。我倾向于说它 is 替换失败,在这种情况下,clang++ 是对的,g++ 是错误的。正如 David Rodríguez 指出的那样,其背后的问题是模板参数的上下文中不允许从 void (A::*)(int) 转换为 void (B::*)(int)
  • @KerrekSB:这不会是对 T2::Func() 的测试,对吗? DyP:是的,似乎是问题所在,但是如何使用 GCC 进行测试?

标签: c++ templates sfinae


【解决方案1】:

该问题与已正确解决的类模板无关,而是与成员地址表达式中的一个奇怪的怪癖有关。特别是,类型:

struct base { void foo(); };
struct derived : base {};

表达式&amp;derived::foo 属于void (base::*)() 类型,可能直观也可能不直观。

作为检测成员函数模板是否存在的测试,我没有答案。您不能获取模板的地址,但您可能会创建一个虚假的不可访问类型并尝试使用该类型调用函数。类可以具有采用该类型的函数的唯一方法是函数本身是模板。您可能希望在未计算的表达式中使用它来避免 odr-using 模板函数与您的类型。

【讨论】:

  • "表达式 &derived::foo 的类型为 void (base::*)()" 这是正确的,但我认为这不是问题所在。 OP 想要检测一个 not 继承的成员函数模板(据我了解 OP)。表达式&amp;A::Func 还没有类型[over.over],但与void (A::*)(int)void (B::*)(int) 都匹配。
  • @DyP: &amp;A::Func 不能与模板上的void (B::*)(int) 匹配,因为它需要从基类的指向成员的指针转换为派生的指向成员的指针,这不是一个非类型模板参数的有效转换。你说得对,我跳过了&amp;A::Func 是一组重载解决方案候选集的部分,但对于某些类型T,它们都是void (A::*)(T)(即A 在整个集合中都是固定的。
  • 是的,一开始我也是这么想的,但是[over.over]里面有个注释:“[注:就是函数所属的类在匹配a时会被忽略指向成员函数类型的指针。-end note]"
  • @DyP:是的,那句话支持了我的观点。重载决议忽略成员的类型,因此它尝试匹配void (derived::*)(int),并且重载决议选择void (base::*)(int)即使base != derived 作为持有该成员的类型被忽略。此时(在重载决议之后)并且根据 5.3.1/3,表达式的类型被确定为 void (base::*)(int) not void (derived::*)(int),即使后者用于驱动重载决议。
  • 请注意,在模板之外这完全没问题,因为存在从 void (base::*)(int)void (derived::*)(int) 的隐式转换,但是当匹配非类型时,该转换不是允许的转换之一模板参数。
【解决方案2】:

我会使用这个修复:

Sfinae<T2, decltype(std::declval<T2>().Func(0))> 

即使用表达式obj.Func(0)类型并将其传递给Sfinae类模板。

这里是完整的修复代码:

#include <iostream>
#include <utility>

struct A { template<class T> void Func( T ) {}; };

struct B : A {};

struct C {};

template< class T >
struct CheckForFunc
{
    typedef char(&YesType)[1];
    typedef char(&NoType)[2];
    template< class, class > struct Sfinae;

    template< class T2 > static YesType Test( Sfinae<T2, decltype(std::declval<T2>().Func(0))> * );
    template< class T2 > static NoType  Test( ... );
    static const bool value = sizeof(Test<T>(0))==sizeof(YesType);
};

int main(int argc, char* argv[])
{
    std::cout << "Value A=" << CheckForFunc< A >::value << std::endl;
    std::cout << "Value B=" << CheckForFunc< B >::value << std::endl;
    std::cout << "Value C=" << CheckForFunc< C >::value << std::endl;
    return 0;
}

输出:

Value A=1
Value B=1
Value C=0

我在这个演示中添加了 C 类。

Online Demo。 :-)

【讨论】:

  • 我认为 OP 想要输出 Value B=0:“我宁愿只检测类的方法,而不是检测派生方法”
  • @DyP:这对我来说没有意义(我也不认为这是可能的),因为基方法一旦被继承,就会成为派生类的类方法。
  • 见大卫罗德里格斯的回答。对于非重载函数(和非模板),表达式&amp;B::Func 的类型是void (A::*)(int)。不过,不知道为什么 OP 需要该检查。
  • @DyP:正确。纳瓦兹:根据to this impossible
【解决方案3】:

更新到 C++20(使用概念) 为我们提供了这个简洁的 constexpr 解决方案

#include <iostream>

struct A { template<class T> void Func( T ) {}; };
struct B : A {};
struct C {};

template <typename T>
concept CheckForFunc = requires(T t) { t.Func(T());  };

int main(int argc, char* argv[])
{
    if constexpr(CheckForFunc<A>)        std::cout << "Value A" << std::endl;
    if constexpr(CheckForFunc<B>)        std::cout << "Value B" << std::endl;
    if constexpr(CheckForFunc<C>)        std::cout << "Value C" << std::endl;
    return 0;
}

输出将是

Value A
Value B

【讨论】:

    【解决方案4】:

    如果您知道成员函数模板应具有的确切模板参数,则此方法有效: (注意它使用了一些可以被 C++03 特性替代的 C++11 特性)

    #include <iostream>
    #include <type_traits>
    
    struct A { template<class T> void Func( T ) {} };
    struct B :A {};
    
    template< class T >
    struct CheckForFunc
    {
        typedef char(&YesType)[1];
        typedef char(&NoType)[2];
    
        template< class T2 > static YesType Test(
            typename std::enable_if<
                std::is_same<void (T::*)(int), decltype(&T2::template Func<int>)>{},
                int
            >::type );
        template< class T2 > static NoType  Test( ... );
        static const bool value = sizeof(Test<T>(0))==sizeof(YesType);
    };
    
    int main(int argc, char* argv[])
    {
        // gives "1"
        std::cout << "Value A=" << CheckForFunc< A >::value << std::endl;
        // doesn't compile!
        std::cout << "Value B=" << CheckForFunc< B >::value << std::endl;
        return 0;
    }
    

    输出:

    值 A=1
    值 B=0

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-06-26
      • 1970-01-01
      • 1970-01-01
      • 2013-09-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多