2015-02-26 3 views
2

Есть ли эквивалент #ifdef для проверки того, существует ли элемент в классе, чтобы обработка могла быть выполнена без сбоя компилятора. Я пробовал работу с шаблонами, но конкретная проблема не удалась.Тест препроцессора C++, если существует член класса

Например

#if member baseclass.memberA() 
    baseclass.memberA().push_back(data); 
#else 
    doAlternate(data); 
#endif 

Очевидно, что выше не является допустимым, но я стараюсь, чтобы узнать, если что-то подобное было добавлено Примечание C++ 11

, что в первоначальной настройке, там будет существовать memberA, memberB, memberC, ... каждый из которых потребует push_back. Другие участники будут добавлены в базовый класс в будущем, поэтому я хочу создать шаблон, чтобы все случаи были скомпилированы и обработаны должным образом, даже если текущий базовый класс не имеет некоторых из членов (например, memberX). В противном случае я могу просто вставить строку push_back() с помощью очень простого шаблона.

Это на самом деле самый простой случай. Существует также случай, когда я создаю экземпляр подкласса, а затем возвращаю его обратно в элемент подкласса.

// Instantiate an element of the Maindata class 
::basedata::Maindata maindata; 
//Instantiate an element of the Subdata class 
::basedata::Subdata subinfo("This goes into the subinfo vector"); 
// Process some data that is part of the Subdata class 
subinfo.contexts(contextInfo); 
// Push the instantiated Subdata into the Subdata member of Maindata 
maindata.subdata().push_back(subinfo); 

Обратите внимание, что необходимо как Subdata и subdata() должен быть установлен таким образом, что соответствующий код реализован. Однако, если :: basedata :: Subdata существует, тогда будет maindata.subdata().

Я уже пробовал различные методы с использованием шаблонов, и конкретная проблема не была разрешена с различными полученными ответами. Примерами являются template instantiation check for member existing in class, C++ class member check if not a template, C++ template for variable type declaration

+3

Это можно сделать с помощью шаблонов, см. Http://stackoverflow.com/a/257382/1111028 – scaryrawr

+0

@scaryrawr Этот ответ не решает проблему. Я уже пробовал. Я смог создать шаблонное решение **, если ** член существует, но не смог заставить шаблоны работать, когда член не выходит. – sabbahillel

ответ

0

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

У меня есть несколько классов с различными членами, все из которых имеют операции следующего типа

::basedata::Maindata maindata; 
maindata.subdata().push_back(subinfo); 
auto inData maindata.subdata().back(); 
inData.contexts(contextInfo); 

Шаблон имеет вид (который предполагает, что класс subdata() не существует (например, memberA в вопросе).

template<typename T, typename D> 
auto doPush(D myData, T & myFormat, contextType contextInfo) 
    -> decltype(myFormat.push_back(myData), bool()) 
{ 
    myFormat.push_back(myData); 
    setcontexts(myFormat.back(), contextInfo) 
    // Since the push-back() was already done, the new data gets entered 
    return true; 
} 

Это делает вызов шаблона

doPush(dataset, maindata.subdata(), contextInfo); 

Поскольку это предполагает, что subdata() существует, нам нужно настроить явный тест для члена, представленного подданными, и сделать его тонкой оболочкой вокруг вызова общего шаблона.

template<typename T, typename D> 
auto createMember(D myData, T & myFormat, contextType contextInfo) 
    -> decltype(myFormat.Member(), bool()) 
{ 
    dopush(myData, myFormat.Member(), myData); 
    return true; 
} 

Обратите внимание, что для этого требуется только три места для имени элемента для ввода с помощью макроса, если имеется достаточно места, чтобы требовать один.

Фактический код затем вызывает шаблон createMember.

Похоже, это было бы самым простым решением.

Я не показывал шаблоны ложных случаев, поскольку они очевидны.

3

Не с препроцессором, но после может помочь:

#include <cstdint> 

#define DEFINE_HAS_SIGNATURE(traitsName, funcName, signature)    \ 
    template <typename U>             \ 
    class traitsName              \ 
    {                  \ 
    private:                \ 
     template<typename T, T> struct helper;        \ 
     template<typename T>            \ 
     static std::uint8_t check(helper<signature, &funcName>*);   \ 
     template<typename T> static std::uint16_t check(...);    \ 
    public:                 \ 
     static                \ 
     constexpr bool value = sizeof(check<U>(0)) == sizeof(std::uint8_t); \ 
    } 

DEFINE_HAS_SIGNATURE(has_memberA, T::memberA, std::vector<int> (T::*)(void)); 

И затем, используя SFINAE:

#include <type_traits> 

template <typename T> 
std::enable_if<has_memberA<T>::value>::type 
doBase(T& t, int data) 
{ 
    t.memberA().push_back(data); 
} 

template <typename T> 
std::enable_if<!has_memberA<T>::value>::type 
doBase(T& , int data) 
{ 
    doAlternate(data); 
} 
+0

Спасибо. Я попробую это и посмотрю, что он делает. Очевидно, я не буду использовать ключевое слово «делать», но создаст другое имя. – sabbahillel

+0

Должен ли я писать отдельный шаблон в файле enable_if для каждого члена или мне нужно создать другой макрос. Главное, что я хочу создать общую ситуацию, так как у меня есть несколько случаев, каждый из которых имеет свой собственный член. – sabbahillel

+0

@sabbahillel: макрос здесь, чтобы позволить создавать черты в одной строке. то вы должны использовать 'enable_if' для каждой ситуации в соответствии с вашими чертами. – Jarod42

4

Это просто еще один случай для void_t.

Нам нужен небольшой вспомогательный шаблон Void и укажите псевдоним типа шаблона удобства void_t.

#include <type_traits> 

template<typename...> 
struct Void { using type = void; }; 

template<typename... T> 
using void_t = typename Void<T...>::type; 

Мы определяем основной шаблон, который реализует политику возврата.

template<typename T, typename = void> 
struct Helper 
{ 
    static void 
    function(T& t) 
    { 
    std::cout << "doing something else with " << &t << std::endl; 
    } 
}; 

и обеспечивает частичную специализацию для типов, которые поддерживают определенную операцию, в данном случае, .data().push_back(int).

template<typename T> 
struct Helper<T, void_t<decltype(std::declval<T>().data().push_back(0))>> 
{ 
    static void 
    function(T& t) 
    { 
    std::cout << "pushing back data to " << &t << std::endl; 
    t.data().push_back(42); 
    } 
}; 

Чтобы скрыть детали реализации Helper от наших клиентов и позволить тип вычет для параметров шаблона, можно красиво обернуть его.

template<typename T> 
void 
function(T& t) 
{ 
    Helper<T>::function(t); 
} 

И так наши клиенты используют его.

#include <iostream> 
#include <vector> 

class Alpha 
{ 
public: 
    std::vector<int>& data() { return this->data_; } 
private: 
    std::vector<int> data_ {}; 
}; 

class Beta { /* has no data() */ }; 

int 
main() 
{ 
    Alpha alpha {}; 
    Beta beta {}; 
    std::cout << "&alpha = " << &alpha << std::endl; 
    std::cout << "&beta = " << &beta << std::endl; 
    function(alpha); 
    function(beta); 
} 

Возможный выход:

&alpha = 0x7ffffd2a3eb0 
&beta = 0x7ffffd2a3eaf 
pushing back data to 0x7ffffd2a3eb0 
doing something else with 0x7ffffd2a3eaf 

Update: Как применить этот метод к нескольким членам

Методика показано выше, может быть применен к любому числу членов. Давайте составим небольшой пример. Скажем, мы хотим написать шаблонную функцию frobnicate, которая принимает аргумент универсального типа и, если объект имеет ...

  • ... функция incr элемента, который не принимает никаких аргументов, назвать его,
  • ... членом name данных, приложите к нему текст, если это возможно, и
  • ... элемент данных numbers, push_back некоторые номера к нему, если это возможно.

Я действительно рекомендую вам решить эту проблему путем реализации три Helper struct сек, как показано выше. Это не так много избыточного ввода и делает для более чистого кода.

Однако, если вы хотите проигнорировать этот совет, давайте посмотрим, как мы можем уменьшить типизацию с помощью макроса. Предполагая то же определение void_t, как показано выше, мы можем определить следующий макрос.

#define MAKE_SFINAE_HELPER(NAME, TYPE, OPERATION, ARGS, CODE)   \ 
    template<typename TYPE, typename = void>        \ 
    struct NAME               \ 
    {                  \ 
    template<typename... AnyT>           \ 
    void                \ 
    operator()(AnyT&&...) noexcept          \ 
    {                 \ 
     /* do nothing */             \ 
    }                 \ 
    };                 \ 
                     \ 
    template<typename TYPE>            \ 
    struct NAME<TYPE, void_t<decltype(std::declval<TypeT>()OPERATION)>> \ 
    {                  \ 
    void operator()ARGS noexcept(noexcept(CODE))      \ 
    {                 \ 
     CODE;                \ 
    }                 \ 
    }; 

Он будет определять struct называется NAME шаблонными от параметра типа TYPE и определить первичный шаблон с оператором (), который принимает любое количество аргументов любого типа и абсолютно ничего не делает. Это используется как резерв, если желаемая операция не поддерживается.

Однако, если объект типа TYPE поддерживает операцию OPERATION, затем частичную специализацию с оператором (), который принимает параметры ARGS и выполняет CODE будет использоваться. Макрос определяется таким образом, что ARGS может быть списком аргументов в скобках. К сожалению, грамматика препроцессора допускает только одно выражение, которое должно быть передано как CODE. Это не большая проблема, так как мы всегда можем написать один вызов функции, который делегирует другую функцию. (Помните, что любая проблема в информатике может быть решена путем добавления дополнительного уровня косвенности - за исключением, конечно, проблемы слишком большого уровня косвенности ...) Оператор () частичной специализации будет объявлен noexcept тогда и только тогда, когда CODE есть. (Это также работает только потому, что CODE ограничен одним выражением.)

Причина, по которой оператор () для основного шаблона является шаблоном, заключается в том, что в противном случае компилятор мог бы выпустить предупреждения о неиспользуемых переменных. Конечно, вы можете изменить макрос, чтобы принять дополнительный параметр FALLBACK_CODE, который помещается в тело оператора основного шаблона (), который должен использовать тот же ARGS.

В самых простых случаях, можно было бы объединить OPERATION и параметр CODE в один, а затем CODE не может относиться к ARGS, который эффективно ограничивает ARGS к одному параметру типа TYPE в этом случае вы могли бы избавиться от этот параметр также, если вам не нужна гибкость.

Итак, применим это к нашей проблеме. Во-первых, нам нужна вспомогательная функция для возврата чисел, потому что это не может быть написано (по крайней мере, давайте сделаем это) как одно выражение. Я делаю эту функцию как можно более общей, делая только предположения о имени члена.

template<typename ObjT, typename NumT> 
void 
do_with_numbers(ObjT& obj, NumT num1, NumT num2, NumT num3) 
{ 
    obj.numbers.push_back(num1); 
    obj.numbers.push_back(num2); 
    obj.numbers.push_back(num3); 
} 

Поскольку две другие желаемые операции легко могут быть записаны в виде одного выражения, нам не нужна дальнейшая косвенность для них. Итак, теперь мы можем создавать наши помощники SFINAE.

MAKE_SFINAE_HELPER(HelperIncr, 
        TypeT, 
        .incr(), 
        (TypeT& obj), 
        obj.incr()) 

MAKE_SFINAE_HELPER(HelperName, 
        TypeT, 
        .name += "", 
        (TypeT& obj, const std::string& appendix), 
        obj.name += appendix) 

MAKE_SFINAE_HELPER(HelperNumbers, 
        TypeT, 
        .numbers.push_back(0), 
        (TypeT& obj, int i1, int i2, int i3), 
        do_with_numbers(obj, i1, i2, i3)) 

Оборудовано с этим, мы можем, наконец, написать нашу frobnicate функцию. Это очень просто.

template<typename T> 
void 
frobnicate(T& object) 
{ 
    HelperIncr<T>()(object); 
    HelperName<T>()(object, "def"); 
    HelperNumbers<T>()(object, 4, 5, 6); 
} 

Чтобы увидеть, что все работает, давайте сделаем два struct сек, что частично поддерживают операции в вопросе.

#include <string> 
#include <vector> 

struct Widget 
{ 
    std::vector<int> numbers {1, 2, 3}; 
    int counter {}; 
    void incr() noexcept { this->counter += 1; } 
}; 

struct Gadget 
{ 
    std::string name {"abc"}; 
    int counter {}; 
    void incr() noexcept { this->counter += 1; } 
}; 

Так как я хочу, чтобы напечатать их, давайте также определить операторы <<.

#include <iostream> 

std::ostream& 
operator<<(std::ostream& os, const Widget& w) 
{ 
    os << "Widget : { counter : " << w.counter << ", numbers : ["; 
    int i {}; 
    for (const auto& v : w.numbers) 
    os << (i++ ? ", " : "") << v; 
    os << "] }"; 
    return os; 
} 

std::ostream& 
operator<<(std::ostream& os, const Gadget& g) 
{ 
    os << "Gadget : { counter : " << g.counter << ", " 
    << "name = \"" << g.name << "\" }"; 
    return os; 
} 

И там мы идем:

int 
main() 
{ 
    Widget widget {}; 
    Gadget gadget {}; 
    std::cout << widget << "\n" << gadget << "\n\n"; 
    frobnicate(widget); 
    frobnicate(gadget); 
    std::cout << widget << "\n" << gadget << "\n"; 
} 

Выход:

Widget : { counter : 0, numbers : [1, 2, 3] } 
Gadget : { counter : 0, name = "abc" } 

Widget : { counter : 1, numbers : [1, 2, 3, 4, 5, 6] } 
Gadget : { counter : 1, name = "abcdef" } 

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

+0

В моем вопросе желаемым результатом является base.sub(). Push_back (data0, в то время как вы помещаете данные в положение базы sub всегда, и мне нужно определить, является ли sub действительным. Альтернативный элемент должен находиться вне шаблона в вызывающей процедуре – sabbahillel

+0

Я не вижу вашей точки. Вы можете заменить выражение внутри 'decltype' тем, что вы хотите проверить, будет ли оно компилироваться. Если вы покажете мне, каковы ваши две альтернативы, я могу обновить мои ответьте соответственно, но это должно быть тривиальное преобразование. – 5gon12eder

+0

В этом конкретном случае я хотел бы иметь шаблон с TU(). push_back (data) При необходимости я могу сначала сохранить T постоянным значением для тестирования См. Также более сложный случай, который я добавил – sabbahillel

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