C++利用显式参数检测自由函数的存在

Azo*_*oth 8 c++ function-pointers sfinae name-lookup c++11

我正在编写一些类型特征,以查看是否存在具有一组特定参数的自由函数.这些函数的签名看起来像这样:

template <class T> void func( SomeClass &, SomeType const & );
Run Code Online (Sandbox Code Playgroud)

我知道前面的时间值T,SomeClassSomeType.如果此函数与这些参数完全相同,我希望特征返回true,而不是使用任何隐式转换.

我可以通过使用SFINAE尝试调用它来轻松编写一些代码来检测此函数是否存在,例如

// potentially in some namespace
template <class> void func(); // this is necessary since user implementations
                              // of func will not exist until after 
                              // this has been defined
template <class X, class Y, class Z>
static auto test(int) -> 
  decltype( func<X>( std::declval<Y&>(), std::declval<Z const&>(), std::true_type());

template <class, class, class> static std::false_type test(...);
Run Code Online (Sandbox Code Playgroud)

并适当地测试这些功能的返回类型.由于我将pass SomeClass(Y)传递给函数,因此ADL可以让编译器查看相应的命名空间,以免被func我为测试定义的虚拟版本混淆.

我遇到的问题是,由于SomeType(Z在上面的测试中)通过常量引用传递,因此允许隐式转换为其他类型.例如,有人可以定义一个函数,如:template <class T> void func( SomeClass &, double const & );对于任何算术类型Z,我的测试将通过.我希望它只在Z真正的类型时通过,在这种情况下是a double.

我试图通过在如下方案中使用函数指针来解决这个问题:

// struct to prevent implicit conversion and enforce function signature
template <class Y, class Z>
struct NoConvert
{
  using FPType = void (*)(Y&, Z const &);
  explicit NoConvert( FPType );
};

template <class> void func(); // see note on why this is needed above

template <class X, class Y, class Z>
static auto test(int) -> decltype( NoConvert( &func<X> ), std::true_type() );
template <class, class, class>
static std::false_type test(...);

template <class X, class Y, class Z>
static bool value(){ return std::is_same<decltype(test<X, Y, Z>()), std::true_type>::value; }
Run Code Online (Sandbox Code Playgroud)

理论上这可以很好地工作,但我遇到的问题是func测试不会看到后来定义的用户版本- 它只看到func我需要为编译器定义的虚拟对象.不幸的是我没有在SomeClass这里传递类型,所以ADL无法启动&func<X>以查找以后定义的用户函数.

有什么方法可以做到这一点吗?解决方案不必使用函数指针,它只需要是一个特性,如果某些自由函数存在且只有一组提供的参数,则返回true.

有关所需行为的参考:

template <class T> void func( A &, int const & );

value<T, A, int>(); // return true
value<T, A, long>(); // return false
value<T, A, double>(); // return false
value<U, A, int>(); // return false
value<T, B, int>(); // return false
Run Code Online (Sandbox Code Playgroud)

Azo*_*oth 7

由于dyp的帮助,我能够使用以下技术解决这个问题:

此解决方案不再使用函数指针,而是依赖于阻止隐式转换的代理类:

template <class Source>
struct NoConvert
{
  template <class Dest, class = typename std::enable_if<std::is_same<Source, Dest>::value>::type>
  operator Dest () const = delete;

  template <class Dest, class = typename std::enable_if<std::is_same<Source, Dest>::value>::type>
  operator Dest const & () const;
};

template <class> void func();

template <class A, class T, class U>
static auto test(int) -> decltype( func<A>( std::declval<T&>(), NoConvert<U>() ), std::true_type() );

template <class, class, class>
static std::false_type test(...);

template <class A, class T, class U>
static bool valid()
{
  return std::is_same<decltype(test<A, T, U>(0)), std::true_type>::value;
}
Run Code Online (Sandbox Code Playgroud)

可以使用如下:

template <class T>
void func( B &, int const & );

template <class T>
void func( B &, std::string );

template <class T>
void func( A &, std::string const & );

std::cout << valid<A, B, int>() << std::endl;         // true
std::cout << valid<A, B, std::string>() << std::endl; // false
std::cout << valid<A, A, std::string>() << std::endl; // true
Run Code Online (Sandbox Code Playgroud)

通过使用内部的转换运算符NoConvert,您可以通过传递值,引用或常量引用来实现此功能.

例如,在当前使用中,当转换运算符NoConvert<std::string>由值参数触发时std::string,两个重载都是有效的,因此存在歧义,这意味着SFINAE将剔除此并允许std::false_type测试通过.在常量引用参数的情况下,常量引用过载具有优先权并且正确地允许std::true_type测试过载通过.

此解决方案还依赖于使用ADL来解析函数名称的能力,这在函数指针方法中是不可能的.