2015-02-03 4 views
2

Есть целый ряд вопросов, ответов о проверке, существует ли функция члена: например, Is it possible to write a template to check for a function's existence?Проверка существования (перегруженный) функция члена

Но этот метод терпит неудачу, если функция перегружена. Вот немного измененный код из самого высокого ответа этого вопроса.

#include <iostream> 
#include <vector> 

struct Hello 
{ 
    int helloworld(int x) { return 0; } 
    int helloworld(std::vector<int> x) { return 0; } 
}; 

struct Generic {}; 


// SFINAE test 
template <typename T> 
class has_helloworld 
{ 
    typedef char one; 
    typedef long two; 

    template <typename C> static one test(decltype(&C::helloworld)) ; 
    template <typename C> static two test(...); 


public: 
    enum { value = sizeof(test<T>(0)) == sizeof(char) }; 
}; 


int 
main(int argc, char *argv[]) 
{ 
    std::cout << has_helloworld<Hello>::value << std::endl; 
    std::cout << has_helloworld<Generic>::value << std::endl; 
    return 0; 
} 

Этот код выводит:

0 
0 

Но:

1 
0 

если второй helloworld() закомментирована.

Итак, мой вопрос в том, можно ли проверить, существует ли функция-член, независимо от того, перегружена ли она.

+0

хотите ли вы знать, если существует функция *, если вызывается с определенными параметрами *? Какие компиляторы вам нужны для поддержки? Какие стандартные версии? – Yakk

+0

Достаточно знать, что он существует с любыми параметрами. Я доволен C++ 11, но сказал, что хочу поддержать как можно больше компиляторов. – foxcub

+0

Говоря о компиляторах, я только заметил, что код как данный компилируется и печатает '0 0' с Clang 3.5.1, но он не компилируется с GCC 4.9.2. – foxcub

ответ

9

В C++ невозможно [до сих пор] принять адрес набора перегрузки: когда вы берете адрес функции или функции-члена, функция либо уникальна, либо необходимо выбрать соответствующий указатель, например, путем непосредственного перехода указателя к подходящей функции или путем его литья. Иначе говоря, выражение &C::helloworld терпит неудачу, если helloworld не является уникальным. Насколько я знаю, результат состоит в том, что невозможно определить, присутствует ли перегруженное имя как член класса или как нормальная функция.

Как правило, вам нужно что-то сделать с именем. То есть, если достаточно знать, присутствует ли функция и можно вызвать с набором аргументов указанного типа, вопрос станет много другим: на этот вопрос можно ответить, попытавшись вызвать соответствующий вызов и определить его тип в SFINAE-умелым контексте, например:

template <typename T, typename... Args> 
class has_helloworld 
{ 
    template <typename C, 
       typename = decltype(std::declval<C>().helloworld(std::declval<Args>()...))> 
    static std::true_type test(int); 
    template <typename C> 
    static std::false_type test(...); 

public: 
    static constexpr bool value = decltype(test<T>(0))::value; 
}; 

вы бы затем использовать этот тип, чтобы определить, есть элемент, который соответствующим образом может быть вызван, например:

std::cout << std::boolalpha 
      << has_helloworld<Hello>::value << '\n'  // false 
      << has_helloworld<Hello, int>::value << '\n' // true 
      << has_helloworld<Generic>::value << '\n'; // false 
+0

Супер. Спасибо за объяснение. – foxcub

2
// use std::void_t in C++... 17 I think? ... instead of this: 
template<class...>struct void_type { using type = void; }; 
template<class...Ts>using void_t = typename void_type<Ts...>::type; 

template<class T, class...Args> 
using hello_world_ify = decltype(
     std::declval<T>().helloworld(std::declval<Args>()...) 
); 

template<class T, class Sig, class=void> 
struct has_helloworld:std::false_type{}; 

template<class T, class...Args> 
struct has_helloworld<T, void(Args...), 
    void_t< 
    hello_world_ify<T, Args...> 
    > 
>:std::true_type{}; 

template<class T, class R, class...Args> 
struct has_helloworld<T, R(Args...), 
    typename std::enable_if< 
    std::is_convertible< 
     hello_world_ify<T, Args...>, 
     R 
    >::value 
    >::type 
>:std::true_type{}; 

live example

Я бы поставил выше в details пространства имен, а затем выставить template<class T, class Sig> struct has_helloworld:details::has_helloworld<T,Sig>{}; поэтому кто-то не проходит тип вместо дефолта void.

Мы используем SFINAE для обнаружения, можем ли мы вызывать T.helloworld(Args...). Если переданная в подписи void(blah), мы просто обнаруживаем, может ли возникнуть вызов - если нет, мы проверяем, что тип возврата T.helloworld(Args...) может быть преобразован в тип возврата подписи.

MSVC имеет серьезные проблемы с SFINAE с decltype, поэтому вышеуказанное может не работать в MSVC.

Обратите внимание, что has_helloworld<T, R(Args...)> соответствует прохождению в RValue T, вызывая helloworld в этом контексте RValue Попутно Rvalue Args.... Чтобы сделать значения lvalues, добавьте &. Чтобы сделать их const lvalues, используйте const& для типов. Тем не менее, в большинстве случаев это должно быть только вопросом.

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

Вышеупомянутые могут быть адаптированы для точных совпадений подписи.

Любопытно, что при наличии конфликта подписи (т. Е. Произошла ошибка в непосредственном контексте вызова), он действует так, как будто такого метода нет.

Поскольку он полагается на SFINAE, ошибки в небезопасном контексте не вызывают сбоев.

Смежные вопросы