Обнаружение функции в C ++ во время компиляции - PullRequest
6 голосов
/ 18 января 2012

Есть ли способ, предположительно используя шаблоны, макросы или их комбинацию, чтобы я мог в общем случае применять функцию к различным классам объектов, но заставлять их реагировать по-разному, если у них нет определенной функции?

Я специально хочу применить функцию, которая будет выводить размер объекта (то есть количество объектов в коллекции), если у объекта есть эта функция, но будет выводить простую замену (например, «N / A»)если объект неТ.е.

NO_OF_ELEMENTS( mySTLMap ) -----> [ calls mySTLMap.size() to give ] ------>  10
NO_OF_ELEMENTS( myNoSizeObj ) --> [ applies compile time logic to give ] -> "N/A"

Я ожидаю, что это может быть чем-то похожим на статическое утверждение, хотя я бы явно хотел скомпилировать другой путь к коду, а не потерпеть неудачу на этапе сборки.

Ответы [ 6 ]

12 голосов
/ 18 января 2012

Из того, что я понимаю, вы хотите сделать общий тест, чтобы увидеть, имеет ли класс определенную функцию-член. Это может быть выполнено в C ++ с использованием SFINAE . В C ++ 11 это довольно просто, так как вы можете использовать decltype:

template <typename T>
struct has_size {
private:
    template <typename U>
    static decltype(std::declval<U>().size(), void(), std::true_type()) test(int);
    template <typename>
    static std::false_type test(...);
public:
    typedef decltype(test<T>(0)) type;
    enum { value = type::value };
};

Если вы используете C ++ 03, это будет немного сложнее из-за отсутствия decltype, поэтому вместо этого вы должны использовать sizeof:

template <typename T>
struct has_size {
private:
    struct yes { int x; };
    struct no {yes x[4]; };
    template <typename U>
    static typename boost::enable_if_c<sizeof(static_cast<U*>(0)->size(), void(), int()) == sizeof(int), yes>::type test(int);
    template <typename>
    static no test(...);
public:
    enum { value = sizeof(test<T>(0)) == sizeof(yes) };
};

Конечно, здесь используется Boost.Enable_If, что может быть нежелательной (и ненужной) зависимостью. Тем не менее, написание enable_if себя невероятно просто:

template<bool Cond, typename T> enable_if;
template<typename T> enable_if<true, T> { typedef T type; };

В обоих случаях сигнатура метода test<U>(int) видна только в том случае, если U имеет метод size, так как в противном случае оценка либо decltype, либо sizeof (в зависимости от используемой версии) завершится неудачно. , который затем удалит метод из рассмотрения (из-за SFINAE. Длинные выражения std::declval<U>().size(), void(), std::true_type() являются злоупотреблением оператором запятой C ++, который будет возвращать последнее выражение из списка, разделенного запятыми, поэтому это гарантирует, что тип известный как std::true_type для варианта C ++ 11 (а sizeof оценивает int для варианта C ++ 03). void() в середине только там, чтобы убедиться, что нет никаких странных перегрузок оператор запятой, мешающий оценке.

Конечно, это вернет true, если T имеет метод size, который можно вызывать без аргументов, но не дает никаких гарантий относительно возвращаемого значения. Я предполагаю, что вы, вероятно, захотите обнаружить только те методы, которые не возвращают void. Это может быть легко достигнуто с небольшой модификацией метода test(int):

// C++11
template <typename U>
static typename std::enable_if<!is_void<decltype(std::declval<U>().size())>::value, std::true_type>::type test(int);
//C++03
template <typename U>
static typename std::enable_if<boost::enable_if_c<sizeof(static_cast<U*>(0)->size()) != sizeof(void()), yes>::type test(int);
9 голосов
/ 18 января 2012

Несколько раз назад была дискуссия о способностях constexpr. Пришло время использовать его, я думаю:)

С помощью constexpr и decltype можно легко создать черту:

template <typename T>
constexpr decltype(std::declval<T>().size(), true) has_size(int) { return true; }

template <typename T>
constexpr bool has_size(...) { return false; }

Так легко, что черта теряет большую часть своей ценности:

#include <iostream>
#include <vector>

template <typename T>
auto print_size(T const& t) -> decltype(t.size(), void()) {
  std::cout << t.size() << "\n";
}

void print_size(...) { std::cout << "N/A\n"; }

int main() {
  print_size(std::vector<int>{1, 2, 3});
  print_size(1);
}

В действии :

3
N/A
4 голосов
/ 18 января 2012

Это можно сделать с помощью техники под названием SFINAE . В вашем конкретном случае вы можете реализовать это, используя Boost.Concept Check . Вы должны написать свою собственную концепцию для проверки size -метода. В качестве альтернативы вы можете использовать существующую концепцию, такую ​​как Container, которая, среди прочего, требует size -метод.

3 голосов
/ 18 января 2012

Вы можете сделать что-то вроде

template< typename T>
int getSize(const T& t)
{
    return -1;
}

template< typename T>
int getSize( const std::vector<T>& t)
{
    return t.size();
}

template< typename T , typename U>
int getSize( const std::map<T,U>& t)
{
    return t.size();
}

//Implement this interface for 
//other objects
class ISupportsGetSize
{
public:
    virtual int size() const= 0;
};

int getSize( const ISupportsGetSize & t )
{
    return t.size();
}

int main()
{
    int s = getSize( 4 );
    std::vector<int> v;
    s = getSize( v );

    return 0;
}

в основном, наиболее общая реализация всегда возвращает -1 или «NA», но для вектора и карт она возвращает размер. Поскольку самый общий всегда совпадает, никогда не происходит сбой во время сборки

2 голосов
/ 18 января 2012

Вот, пожалуйста. Замените std::cout выводом по своему вкусу.

template <typename T>
class has_size
{
    template <typename C> static char test( typeof(&C::size) ) ;
    template <typename C> static long test(...);

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

template<bool T>
struct outputter
{
    template< typename C >
    static void output( const C& object )
    {
        std::cout << object.size();
    }
};

template<>
struct outputter<false>
{
    template< typename C >
    static void output( const C& )
    {
        std::cout << "N/A";
    }
};


template<typename T>
void NO_OF_ELEMENTS( const T &object )
{
    outputter< has_size<T>::value >::output( object );
}
1 голос
/ 18 января 2012

Вы можете попробовать что-то вроде:

#include <iostream>
#include <vector>



template<typename T>                                                                
struct has_size                                                                 
{                                                                                   
  typedef char one;                                                                 
  typedef struct { char a[2]; } two;                                                

  template<typename Sig>                                                            
  struct select                                                                     
  {                                                                                 
  };                                                                                

  template<typename U>                                                              
  static one check (U*, select<char (&)[((&U::size)!=0)]>* const = 0);     
  static two check (...);                                                           

  static bool const value = sizeof (one) == sizeof (check (static_cast<T*> (0)));   
};



struct A{ };
int main ( )
{
    std::cout << has_size<int>::value << "\n";
    std::cout << has_size<A>::value << "\n";
    std::cout << has_size<std::vector<int>>::value << "\n";
}

, но вы должны быть осторожны, это не работает, когда size перегружен, или когда это шаблон.Когда вы можете использовать C ++ 11, вы можете заменить вышеупомянутый трюк sizeof на decltype magic

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