8

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

template <class T> void func( SomeClass &, SomeType const & );

我提前知道TSomeClass和的值SomeType。如果这个函数完全存在这些参数,我希望特征返回 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(...);

并适当地测试这些函数的返回类型。由于我在这里将SomeClass( Y) 传递给函数,ADL 可以让编译器在适当的名称空间中查找,以免被func我为测试定义的虚拟版本混淆。

我在这里遇到的问题是,由于SomeType(Z在上面的测试中) 是通过常量引用传递的,所以它可以隐式转换为其他类型。例如,有人可以定义一个函数,例如:template <class T> void func( SomeClass &, double const & );对于任何算术类型 for 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; }

理论上这会很好用,但我遇到的问题是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
4

1 回答 1

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

可以像这样使用:

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

通过使用 内部的转换运算符NoConvert,您可以通过值、引用或常量引用来实现这一点。

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

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

于 2014-03-17T22:36:59.877 回答