【问题标题】:Check if a class has a method with a given name but any signature检查一个类是否有一个给定名称但有任何签名的方法
【发布时间】:2019-06-11 16:43:55
【问题描述】:

我正在尝试找到一种方法,使用 c++11 功能简单地检查给定名称的方法是否存在于 c++ 类中,但不检查签名。

没有签名检查我无法找到任何东西,所以我尝试使用来自here 的 Valentin Milea 的解决方案并对其进行修改(见下文),但我对 c++ 的理解还不够深入,无法真正掌握那里发生的事情:

#include <type_traits>

template <class C>
class HasApproxEqualMethod
{
    template <class T>
    static std::true_type testSignature(bool (T::*)(const T&, double) const);

    template <class T>
    static decltype(testSignature(&T::approx_equal)) test(std::nullptr_t);

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

public:
    using type = decltype(test<C>(nullptr));
    static const bool value = type::value;
};

class Base {
public:
virtual ~Base();
virtual bool approx_equal(const Base& other, double tolerance) const;
};

class Derived : public Base {
public:
     // same interface as base class
    bool approx_equal(const Base& other, double tolerance) const;
};

class Other {};

static_assert(HasApproxEqualMethod<Base>().value == true, "fail Base");
static_assert(HasApproxEqualMethod<Other>().value == false, "fail Other");
 // this one fails:
static_assert(HasApproxEqualMethod<Derived>().value == true, "fail Derived");

我认为问题的根源在于我的approx_equal 在派生类中也使用了基类引用,并且不再与签名匹配。

最后,我想构造一个模板比较函数,如果它存在则调用 approx_equal 或其他东西(例如 == 用于字符串等,或 fabs(a-b) &lt;= tolerance 用于浮点数和双精度数)。然后 approx_equal 函数将依次调用每个成员的模板比较。

实际上,我让这部分使用了一个丑陋的解决方法,其中每个具有approx_equal 方法的类也获得一个成员变量const static char hasApproxEqualMethod。然后我检查该变量是否按照here 的建议存在,但这肯定不是要走的路。

【问题讨论】:

  • this question 的答案应该会派上用场。只需将operator() 替换为approx_equal

标签: c++ c++11 templates methods sfinae


【解决方案1】:

如何使用std::is_member_function_pointer_v(需要c++17):

// Works
static_assert(std::is_member_function_pointer_v<decltype(&Base::approx_equal)>); 
// Works
static_assert(std::is_member_function_pointer_v<decltype(&Derived::approx_equal)>); 
// Fails as expected
static_assert(!std::is_member_function_pointer_v<decltype(&Other::approx_equal)>); 

你可以这样缩短它:

template<typename Class> 
constexpr bool has_approx_equal()
{
    return std::is_member_function_pointer_v<decltype(&Class::approx_equal)>; 
}

static_assert(has_approx_equal<Base>()); 
static_assert(has_approx_equal<Derived>()); 
static_assert(!has_approx_equal<Other>());

最终解决方案使用 SFINAE,以便在计算 &amp;Other::approx_equal 时不会中止编译:

template<typename Class, typename Enabled = void> 
struct has_approx_equal_s
{
    static constexpr bool value = false;  
};

template<typename Class> 
struct has_approx_equal_s
<
    Class, 
    std::enable_if_t
    <
        std::is_member_function_pointer_v<decltype(&Class::approx_equal)>
    > 
> 
{
    static constexpr bool value = std::is_member_function_pointer_v<decltype(&Class::approx_equal)>; 
};

template<typename Class> 
constexpr bool has_approx_equal()
{
    return has_approx_equal_s<Class>::value; 
};

static_assert(has_approx_equal<Base>()); 
static_assert(has_approx_equal<Derived>()); 
static_assert(has_approx_equal<Other>(), "Other doesn't have approx_equal.");

SFINAE 确保在尝试评估静态断言之前可以获得false 值。

【讨论】:

    【解决方案2】:

    您的代码中的问题是您检查一个类是否具有接受作为第一个参数的对同一类对象的常量引用的方法

    template <class T>
    static std::true_type testSignature(bool (T::*)(const T&, double) const);
    // .......................................^...........^   same class
    

    但在 Derived 内部定义了一个接收不同类对象的方法 (Base)

    // ...VVVVVVV  object is Derived
    class Derived : public Base {
    public:
         // same interface as base class
        bool approx_equal(const Base& other, double tolerance) const;
        // .....................^^^^  method accept Base
    };
    

    一个可能的解决方案是放宽HasApproxEqualMethod 中的测试以同时接受不同类的对象

    template <class T, class U>
    static std::true_type testSignature(bool (T::*)(const U&, double) const);
    // now class and argument are different...^...........^
    

    这种方式也满足

    static_assert(HasApproxEqualMethod<Derived>().value == true, "fail Derived");
    

    如果你想完全避免签名检查,你可以尝试类似的方法

    template <typename T>
    constexpr auto haemHelper (T const &, int)
       -> decltype( &T::approx_equal, std::true_type{} );
    
    template <typename T>
    constexpr std::false_type haemHelper (T const &, long);
    
    template <typename T>
    using HasApproxEqualMethod = decltype( haemHelper(std::declval<T>(), 0) );
    

    但是,这样一来,Tapprox_equal 方法具有完全不同的签名或 approx_equal 是一个简单的成员(变量)时,std::true_type 也是 std::true_type

    【讨论】:

      【解决方案3】:

      使用 c++11 功能检查给定名称的方法是否存在于 c++ 类中,但不检查签名。

      [..]

      最后,我想构造一个模板比较函数,如果存在就调用 approx_equal

      实际上,您想知道bool(lhs.approx_equal(rhs, some_double)) 是否有效,因此不是精确签名,而是“兼容”签名。并且它用于沿着重载调度。

      因此,您可以使用 decltype 并订购您的重载:

      // helper function to prioritize overload. bigger has more priority
      template <std::size_t N> struct overload_priority : overload_priority<N - 1> {};
      template <> struct overload_priority<0> {}; // Lowest priority
      
      // member function
      template <typename T>
      auto generic_approx_equal_impl(const T& lhs, const T& rhs, double tolerance, overload_priority<2>)
      -> decltype(bool(lhs.approx_equal(rhs, tolerance))
      {
          return lhs.approx_equal(rhs, tolerance);
      }
      
      // free function
      template <typename T>
      auto generic_approx_equal_impl(const T& lhs, const T& rhs, double tolerance, overload_priority<2>)
      -> decltype(bool(approx_equal(lhs, rhs, tolerance))
      {
          return approx_equal(lhs, rhs, tolerance);
      }
      
      // fallback
      template <typename T>
      bool generic_approx_equal_impl(const T& lhs, const T& rhs, double tolerance, overload_priority<0>)
      {
          /*..*/
          //return abs(lhs - rhs) < tolerance;
          return false;
      }
      
      template <typename T>
      bool generic_approx_equal(const T& lhs, const T& rhs, double tolerance)
      {
          // Call with number greater or equal to max overloads
          return generic_approx_equal_impl(lhs, rhs, tolerance, overload_priority<10>{});
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-08-03
        • 1970-01-01
        • 2011-01-11
        • 1970-01-01
        • 1970-01-01
        • 2014-12-09
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多