使用 SFINAE 检测编译时是否存在重载的独立函数

Using SFINAE to detect if overloaded free-standing function exists at compile-time

这是我的 unsuccessful attempt 在 C++98/03 中检测是否存在任何 class/struct T 的独立函数 void set( T& , void (T::*Func)( const char* ) , const char* str )

#include <iostream>
#include <typeinfo>

struct Foo;
struct Bar;
struct Fuu;

void set( Foo& , void (Foo::*Func)( const char* ) , const char* str )
{
}

void set( Bar& , void (Bar::*Func)( const char* ) , const char* str )
{
}

template <typename T>
class setter_is_implemented
{
public: 
    typedef void (*SetterFunction)( T& , void (T::*)( const char* ) , const char* );

    typedef char one;
    typedef long two;

    template <typename C> static one test( C c, SetterFunction = c ) ;
    template <typename C> static two test(...);    

public:
    enum { value = sizeof( test<T>(&set)) == sizeof(one) };
};

int main()
{
    std::cout << setter_is_implemented<Foo>::value << std::endl;
    std::cout << setter_is_implemented<Bar>::value << std::endl;
    std::cout << setter_is_implemented<Fuu>::value << std::endl;
}

GCC 错误信息

Test.cpp:24:66: error: ‘c’ was not declared in this scope
     template <typename C> static one test( C c, SetterFunction = c ) ;
                                                                  ^
Test.cpp: In instantiation of ‘class setter_is_implemented<Foo>’:
Test.cpp:33:44:   required from here
Test.cpp:28:35: error: address of overloaded function with no contextual type information
     enum { value = sizeof( test<T>(&set)) == sizeof(one) };
                                   ^
Test.cpp: In instantiation of ‘class setter_is_implemented<Bar>’:
Test.cpp:34:44:   required from here
Test.cpp:28:35: error: address of overloaded function with no contextual type information
Test.cpp: In instantiation of ‘class setter_is_implemented<Fuu>’:
Test.cpp:35:44:   required from here
Test.cpp:28:35: error: address of overloaded function with no contextual type information

AFAIK,获取重载函数的地址发生在重载解析之前,因此 SFINAE 不能以这种方式工作。这也更难,因为你的函数 returns 无效(否则,人们只会检查结果的大小)。我的 C++03 元编程生锈了,但这应该可以工作 (live on coliru):

namespace setter_is_implemented_detail {
  struct dummy1 { char c[2]; };
  typedef char dummy2;

  dummy2 operator,(dummy1, dummy1);

  template<typename T> dummy1 set(T const&,...);

  template <typename T>
  struct impl
  {
    typedef void (T::*FT)( const char* );
    static T& x;

    enum { value = sizeof( set(x,static_cast<FT>(0),static_cast<const char*>(0)), dummy1() ) != sizeof(dummy2) };
  };
}

template <typename T>
struct setter_is_implemented: setter_is_implemented_detail::impl<T>{};

注意几点:

  • set() 需要在这里被 ADL 找到(没什么大不了的,据我从你的问题描述中可以看出)(*)
  • 由于 void return 类型,需要逗号运算符技巧 (**);在 gcc 中,您 可以 使用 sizeof(void),因此可以在那里简化代码
  • 这不适用于非对象类型(由于 static T&)。不过,如果您需要,它可以正常工作...

(*) 请注意,这不适用于所有可能的 set() 重载;对于任何 sfinae 都是如此,即使在 c++17 中,set() 也必须始终对 sfinae 友好才能工作。

如果您需要精确匹配 sfinae 友好的 set() 重载,仍然可以做到,但更复杂;我会分两个阶段进行:首先你使用上面的代码找到可能的候选人,然后你检查函数指针转换,有点像你在原始代码中所做的..

(**) 这个技巧有效,因为如果 set()void return 类型(因此存在),逗号总是被解释为内置逗号运算符(见c++03[3.9.1/9]: "An expression of type void shall be used only as an expression statement (6.2), as an operand of a comma expression[...]") 产生最右边的表达式。否则,使用虚拟集和逗号运算符。