Проверьте, есть ли у класса метод с заданным именем, но с любой сигнатурой - PullRequest
0 голосов
/ 17 января 2019

Я пытаюсь найти способ просто проверить, существует ли метод с заданным именем в классе c ++ с использованием функций c ++ 11, но без (!) Проверки подписи.

Мне не удалось найти что-либо без проверки подписи, поэтому я попытался использовать решение Валентина Милеи из здесь и изменить его (см. Ниже), но мое понимание 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) <= tolerance для чисел с плавающей запятой и двойных чисел). Затем функции округлого вызова будут вызывать сравнение шаблонов для каждого члена.

Я действительно заставил эту часть работать с уродливым обходным решением, где каждый класс с approx_equal методом также получает переменную-член const static char hasApproxEqualMethod. Затем я проверяю, существует ли эта переменная, как предложено здесь , но это определенно не тот путь.

Ответы [ 3 ]

0 голосов
/ 17 января 2019

Проблема в вашем коде состоит в том, что вы проверяете, что класс имеет метод, который принимает в качестве первого аргумента константную ссылку на объект того же класса

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

но, таким образом, HasApproxEqualMethod<T> равен std::true_type также, когда T имеет метод approx_equal с совершенно другой сигнатурой или также когда approx_equal является простым членом (переменной).

0 голосов
/ 17 января 2019

проверка, существует ли метод с заданным именем в классе c ++ с использованием функций c ++ 11, но без (!) Проверки подписи.

[..]

В конце я хочу создать функцию сравнения шаблонов, которая вызывает приблизительно_эквивалент, если он существует

На самом деле, вы хотите знать, является ли 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>{});
}
0 голосов
/ 17 января 2019

Как насчет использования 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, поэтому компиляция не прерывается при попытке выполнить оценку &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, прежде чем пытаться оценить статическое утверждение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...