compile time class member detection

Тема в разделе "LANGS.C", создана пользователем scf37, 29 мар 2007.

  1. _DEN_

    _DEN_ DEN

    Публикаций:
    0
    Регистрация:
    8 окт 2003
    Сообщения:
    5.383
    Адрес:
    Йобастан
    RedLord

    Да, прекольна. Аффтар маладец! :)
     
  2. RedLord

    RedLord Member

    Публикаций:
    0
    Регистрация:
    23 июн 2005
    Сообщения:
    183
    Адрес:
    Ukraine
    scf37
    проверь на gcc

    Код (Text):
    1. struct Iter
    2. {
    3.     int& operator[](size_t _N) const
    4.     {
    5.         return x;
    6.     }  
    7.    
    8.     mutable int x;
    9. };
    10.  
    11.  
    12. namespace Private
    13. {
    14.     template <class _Tp>
    15.         struct Dummy: _Tp
    16.     {
    17.         operator Dummy*()
    18.         {
    19.             return 0;  
    20.         }  
    21.     };
    22.  
    23.    
    24.     template <class _U>
    25.         char   t2(_U*,  ...)
    26.     {  
    27.         return 0;
    28.     }
    29.  
    30.     template <class _Tp>
    31.     char*   t2(Dummy<_Tp>*, Dummy<_Tp>&)
    32.     {  
    33.         return 0;
    34.     }
    35.  
    36. }
    37.  
    38.  
    39.  
    40. template <class _Tp> struct Check
    41. {
    42.     typedef Private::Dummy<_Tp> Type;
    43.     enum
    44.     {
    45.         value = (sizeof(Private::t2((Type*)0, Type()[size_t(0)]))==sizeof(char))
    46.     };
    47. };
    48.  
    49.  
    50. int main()
    51. {
    52.     int z =  Check<Iter>::value;
    53.     return 0;  
    54. }
     
  3. RedLord

    RedLord Member

    Публикаций:
    0
    Регистрация:
    23 июн 2005
    Сообщения:
    183
    Адрес:
    Ukraine
    scf37
    стандарт читал по диагонали, но шестерка со своей интерпретацией его, вообще расхолаживает :)
     
  4. scf37

    scf37 New Member

    Публикаций:
    0
    Регистрация:
    29 мар 2007
    Сообщения:
    44
    ух... работает... очень красивое решение - сделать для класса оператор преобразования к указателю на этот класс
    зачем нужен темплейтный первый параметр в t2() ? вроде и так работает
    Такой код сойдёт за конечный вариант?
    Код (Text):
    1. #include <list>
    2. #include <vector>
    3. #include <iostream>
    4.  
    5. namespace Private
    6. {
    7.     template <class _Tp>
    8.         struct Dummy: _Tp
    9.     {
    10.         operator Dummy*()
    11.         {
    12.             return 0;  
    13.         }  
    14.     };
    15.  
    16.     struct char2 //sizeof(char2) definitely larger than sizeof(v
    17.     {
    18.         char x[2];
    19.     };
    20.  
    21.     char   t2(int ,  ...)
    22.     {  
    23.         return char();
    24.     }
    25.  
    26.     template <class _Tp>
    27.     char2   t2(int, Dummy<_Tp>&)
    28.     {  
    29.         return char2();
    30.     }
    31.  
    32. }
    33.  
    34. template <class _Tp> struct Check
    35. {
    36.     typedef Private::Dummy<_Tp> Type;
    37.     enum
    38.     {
    39.         value = (sizeof(Private::t2(0, Type()[int(0)]))==sizeof(char))
    40.     };
    41. };
    42.  
    43. template <class _Tp> struct Check<_Tp *>
    44. {
    45.     enum
    46.     {
    47.         value = 1
    48.     };
    49. };
    50.  
    51.  
    52.  
    53. int main()
    54. {
    55.     std::cout << "list: " << Check<std::list<int>::iterator>::value << std::endl;
    56.     std::cout << "vector: " << Check<std::vector<int>::iterator>::value << std::endl;
    57.     std::cout << "raw: " << Check<int *>::value << std::endl;
    58.     return 0;  
    59. }
    Увы, испытания на VC6 показали следующее:
    -std::vector::iterator - голый указатель
    -про частичные cпециализации шаблонов(14.5.4) он ничего не знает
     
  5. RedLord

    RedLord Member

    Публикаций:
    0
    Регистрация:
    23 июн 2005
    Сообщения:
    183
    Адрес:
    Ukraine
    scf37
    для вектора - всегда итератор это "сырой " указатель.
    нужно сначала отфильтровать такой случай
     
  6. scf37

    scf37 New Member

    Публикаций:
    0
    Регистрация:
    29 мар 2007
    Сообщения:
    44
    RedLord
    Не всегда
    Например, в VC2005:
    //vector
    typedef _Vector_iterator<_Ty, _Alloc> iterator;
     
  7. RedLord

    RedLord Member

    Публикаций:
    0
    Регистрация:
    23 июн 2005
    Сообщения:
    183
    Адрес:
    Ukraine
    да. ты прав. по стандарту - implementation defined

    для VC6.0 сырые указатели можно отсечь до основной проверки, использую факт неявного преобразования их в const volatile void*.
    но.
    имеем что-то вроде связанного списка
    Код (Text):
    1. struct Foo
    2. {
    3. int x;
    4. Foo* next; // следующий узел здесь
    5. };
    6.  
    7. Foo* p = ....
    проверка на неявное преобразование для p пройдет, но p+1 - неверно
    т.е. либо на них нужно забить. либо указать в доке, что если это сырой указатель, но блок памяти является непрерывным
     
  8. RedLord

    RedLord Member

    Публикаций:
    0
    Регистрация:
    23 июн 2005
    Сообщения:
    183
    Адрес:
    Ukraine
    это так. поэтому я говорил, что в 8.0 все проще. мой вариант, я проверял на 6.0, Intel 9.0 и самое главное Comeau online test тоже его съел (этого я не ожидал).
    насколько понял - gcc тоже.
    в моем варианте нет частичных специализаций и прочего "стандартного" поведения, к которому у 6.0 аллергия. я бы на твоем месте из-за VC6.0 не рубился - он фактически мертв и оставил реализацию на частичной специализации
     
  9. _DEN_

    _DEN_ DEN

    Публикаций:
    0
    Регистрация:
    8 окт 2003
    Сообщения:
    5.383
    Адрес:
    Йобастан
    Итак, бурные, продолжительные апплодисменты, переходящие в овации :) Вот вам подход, который позволяет детектить мембер-функции, возврящяющие не void. Имя класса, в котором происходит детект - Work.

    EDIT: Теперь можно и void :)

    Код (Text):
    1. struct Work
    2. {
    3.     int foo()
    4.     {
    5.         return 0;
    6.     }
    7. };
    8.  
    9. char foo()
    10. {
    11.     return 0;
    12. }
    13.  
    14. template <class R>
    15. char Proc(R (*)())
    16. {
    17.     return 0;
    18. }
    19.  
    20. template <class T, class R>
    21. int Proc(R (T::*)())
    22. {
    23.     return 0;
    24. }
    25.  
    26. struct A : public Work
    27. {
    28.     enum
    29.     {
    30.         Value = sizeof(Proc(&foo))
    31.     };
    32. };
    33.  
    34. int main()
    35. {
    36.     int x = A::Value;
    37.     return 0;
    38. }
     
  10. _DEN_

    _DEN_ DEN

    Публикаций:
    0
    Регистрация:
    8 окт 2003
    Сообщения:
    5.383
    Адрес:
    Йобастан
    И исчо адин вариант. Инкапсулируем вспомогательные функции + не засоряем глобал неймспейс.

    Код (Text):
    1. struct Work
    2. {
    3.     void foo()
    4.     {
    5.     }
    6. };
    7.  
    8. namespace method_detection
    9. {
    10.  
    11.     char foo()
    12.     {
    13.         return 0;
    14.     }
    15.  
    16.     struct A : public Work
    17.     {
    18.     private:
    19.  
    20.         template <class R>
    21.         static char Proc(R (*)())
    22.         {
    23.             return 0;
    24.         }
    25.  
    26.         template <class T, class R>
    27.         static int Proc(R (T::*)())
    28.         {
    29.             return 0;
    30.         }
    31.  
    32.     public:
    33.  
    34.         enum
    35.         {
    36.             Value = sizeof(Proc(&foo))
    37.         };
    38.     };
    39. }
    40.  
    41. int main()
    42. {
    43.     int x = method_detection::A::Value;
    44.     return 0;
    45. }
     
  11. _DEN_

    _DEN_ DEN

    Публикаций:
    0
    Регистрация:
    8 окт 2003
    Сообщения:
    5.383
    Адрес:
    Йобастан
    Поставим новую задачу :) Написать макрос, который будет учить компиллер детектить заданый метод в заданом классе. ЧТобы было типа так:

    Код (Text):
    1. DECLARE_DETECTION(Window, void, Draw, (int, int))
    2. DECLARE_DETECTION(Window, int, GetWidth, ())
    3. DECLARE_DETECTION(Window, int, GetHeight, ())
    4.  
    5. int main()
    6. {
    7.     bool b1 = method_detection::Window::Draw::Present;
    8.     bool b2 = method_detection::Window::GetWidth::Present;
    9.     bool b3 = method_detection::Window::GetHeight::Present;
    10.     return 0;
    11. }
    Это я конечно схематично. Ну вот чтобы было типа такого :) А там глядишь, может и в буст включат :)))

    EDIT:

    Целесообразнее сделать следующее. Только пока не ясно как обойти невозможность дополнения класса.

    Код (Text):
    1. DECLARE_DETECTION(void, Draw, (int, int))
    2. DECLARE_DETECTION(int, GetWidth, ())
    3. DECLARE_DETECTION(int, GetHeight, ())
    4.  
    5. int main()
    6. {
    7.     bool b1 = method_detection::Detector<Window>::DrawPresent;
    8.     bool b2 = method_detection::Detector<Window>::GetWidthPresent;
    9.     bool b3 = method_detection::Detector<Window>::GetHeightPresent;
    10.     return 0;
    11. }
     
  12. Nouzui

    Nouzui New Member

    Публикаций:
    0
    Регистрация:
    17 ноя 2006
    Сообщения:
    856
    а вот теперь уже я не понял..
     
  13. scf37

    scf37 New Member

    Публикаций:
    0
    Регистрация:
    29 мар 2007
    Сообщения:
    44
    gcc 3.4.5 :
    main.cpp:36: error: ISO C++ forbids taking the address of a bound member function to form a pointer to member function. Say `&method_detection::A::foo'

    Под VC8 всё нормально работает. Я попытался немного переделать код:
    Код (Text):
    1. #include <iostream>
    2.  
    3. namespace member_detection
    4. {
    5. struct char2
    6. {
    7.     char x[2];
    8. };
    9. //detecting
    10. //  int method(char *, int)
    11. int method(char *, int)
    12. {
    13.     return 0;
    14. }
    15.  
    16. char2 Proc(int (*)(char *, int))
    17. {
    18.     return char2();
    19. }
    20.  
    21. template <class T>
    22. char Proc(int (T::*)(char *, int))
    23. {
    24.     return char();
    25. }
    26.  
    27. template<class T>
    28. class Check: public T
    29. {
    30. public:
    31.     enum {Value= (sizeof(Proc(&method)) == sizeof(char)) };
    32. };
    33.  
    34. }
    35.  
    36. class Test
    37. {
    38. public:
    39.     int method(char *, int)
    40.     {
    41.         return 0;
    42.     }
    43. };
    44.  
    45. int main()
    46. {
    47.     std::cout << member_detection::Check<Test>::Value;
    48.     return 0;
    49. }
    Под VC8 всё нормально, gcc компилит, но программа стабильно выдаёт 0 вне зависимости от наличия int method(char *, int)
     
  14. _DEN_

    _DEN_ DEN

    Публикаций:
    0
    Регистрация:
    8 окт 2003
    Сообщения:
    5.383
    Адрес:
    Йобастан
    Nouzui

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

    scf37

    А что скажет Камю?
     
  15. RedLord

    RedLord Member

    Публикаций:
    0
    Регистрация:
    23 июн 2005
    Сообщения:
    183
    Адрес:
    Ukraine
    scf37
    смотрел по диагонали, но похоже проблема из-за двухфазного поиска имен.
     
  16. RedLord

    RedLord Member

    Публикаций:
    0
    Регистрация:
    23 июн 2005
    Сообщения:
    183
    Адрес:
    Ukraine
    _DEN_

    Comeau:
    "ComeauTest.c", line 37: error: nonstandard form for taking the address of a member
    function
    Value = sizeof(Proc(&foo))



    а если структура такая

    intel код не собирает.
     
  17. RedLord

    RedLord Member

    Публикаций:
    0
    Регистрация:
    23 июн 2005
    Сообщения:
    183
    Адрес:
    Ukraine
    scf37
    "Кто хотел определения наличия функции-члена?"
    http://www.rsdn.ru/Forum/Message.aspx?mid=382857&only=1

    приведенный код сам не тестировал
     
  18. green

    green New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2003
    Сообщения:
    1.217
    Адрес:
    Ukraine
    _DEN_
    Вот так можно:

    Код (Text):
    1. #define DECLARE_METHOD_DETECTOR(detectorNameSpace, className, methodReturnValue, methodName, methodArgList) \
    2. namespace detectorNameSpace{    \
    3. template<methodReturnValue(className::*)methodArgList>struct Detector{typedef className Type;}; \
    4. template<class T, class U> struct Exists {enum {val = false};}; \
    5. template<class T> struct Exists<T, class Detector<&T::methodName>::Type>{enum {val = true};};   \
    6. enum {exists = Exists<className, className>::val};      \
    7. }
    8.  
    9. // Пример использования
    10.  
    11. class C
    12. {
    13. public:
    14.     void operator !();
    15.     int Method(char, const void *);
    16. };
    17.  
    18.  
    19. DECLARE_METHOD_DETECTOR(C_operator_not, C, void, operator!, ())
    20. DECLARE_METHOD_DETECTOR(C_Method, C, int, Method, (char, const void *))
    21.  
    22.  
    23. int _tmain(int argc, _TCHAR* argv[])
    24. {
    25.     printf("%d\n", C_operator_not::exists);
    26.     printf("%d\n", C_Method::exists);
    27.     return 0;
    28. }
    ----
    Компилить в VC++ 6 и не пытайтесь. :derisive:
     
  19. Nouzui

    Nouzui New Member

    Публикаций:
    0
    Регистрация:
    17 ноя 2006
    Сообщения:
    856
    green
    в VC8 работает, но на более ранних (в частности 7.1) - нет

    а вообще мощно.. когда я увидел тему, я подумал, что без __if_exists обойтись просто невозможно
     
  20. Nouzui

    Nouzui New Member

    Публикаций:
    0
    Регистрация:
    17 ноя 2006
    Сообщения:
    856
    да, кстати.. изначально речь шла об операторе индексирования, а ошиба C2801 утверждает: 'operator []' must be a non-static member
    это я к тому, что фришный оператор индексирования объявить не так-то легко..