Подтвердить что ты не робот

Выполнение определенной функции зависит от типа

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

Я знаю, что перегрузка - отличное решение для этого. Точно так же,

class C1 {...};
class C2 {...};    
void handle(C1& c1){...}
void handle(C2& c2){...}

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

У меня есть некоторые идеи для моей цели. Например,

class C1 {...};
class C2 {...};

void handle_C1(C1 &c1);
void handle_C2(C2 &c2);

template<typename T>
void handle(T &content){
// Initialization was done here
if (std::is_same(C1, T))
   handle_C1(content);
if (std::is_same(C2, T))
   handle_C2(content);
}

Ошибка компиляции была обнаружена ошибкой, что параметры_сочетания handle_C2, поскольку тип параметра handle_C2 - это C2, когда я вызываю

C1 c1;
handle_C1<C1>(c1);

Согласно SFINAE в С++, я ожидаю, что компилятор проигнорирует эту подстановку, но это не так.

Есть ли кто-нибудь, кто может дать мне совет?

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

4b9b3361

Ответ 1

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

class C1 {...};
class C2 {...};    
void handle_impl(C1& c1){...} // Remove initialization from your overloads
void handle_impl(C2& c2){...}

template<typename T>
void handle(T &content)
{
    // Initialization is done here

    // Let the compiler resolve overloads for you
    handle_impl(content);
}

Ответ 2

Когда handle() создается с помощью T, создается экземпляр полного тела функции. Оба "if" s и их тела скомпилированы. Хотя мы с вами знаем, что is_same() - это постоянная времени компиляции, и вы можете ожидать, что компилятор проигнорирует невозможный случай, то, как он используется, является значением времени выполнения, а компилятор должен обрабатывать как ifs, так и семантически проверенный "как если бы" они были оба потенциально вызваны (даже если оптимизатор может устранить один из них, который после проверки достоверности делает компилятор). Таким образом, вы получаете код, вызывающий handle_C1 и handle_C2, оба прошли один и тот же тип, и один обязательно будет недействительным и не сможет компиляции.

Если вы можете использовать С++ 17, новая функция напрямую решает эту проблему, называемую "constexpr if", которая заставляет тело обработчика if обрабатываться (кроме действительных операторов и синтаксиса), если constexpr истинно:

template<typename T>
void handle(T &content){
// Initialization was done here
if constexpr (std::is_same(C1, T))
   handle_C1(content);
else if constexpr (std::is_same(C2, T))
   handle_C2(content);
}

Если у вас нет компилятора С++ 17 (или даже если вы это делаете!), вам стоит подумать о возврате к исходному дизайну и просто откорректировать инициализацию от каждой из функций и сделать его общим помощником функция.

Ответ 3

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

void handleSpecial(C1& c1) {
    std::cout << "Handling C1\n";
}
void handleSpecial(C2& c2) {
    std::cout << "Handling C2\n";
}
template <typename T>
void handle(T& content) {
    std::cout << "Doing generell stuff\n";
    handleSpecial(content);
}

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

Ответ 4

Выполняя шаги:

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

class CBase { ... common features of C1, C2, C3, ... 
    public:
        ~CBase() {};
        virtual void handle() = 0;
};

class C1 : public CBase { ... something specific to C1
    public:
        virtual void handle() { do what has to be done for C1 }
};
class C2 : public CBase { ... something specific to C2
    public:
        virtual void handle() { do what has to be done for C2 }
};

Теперь, похоже, это уже решит проблему, поскольку вы можете вызвать C1.handle() или CBase- > handle(). Если вам нужно, чтобы это выполнялось с помощью внешней функции, вы могли бы сделать:

void handle(CBase *base_ptr) {
    base_ptr->handle();
}

Лично я считаю, что это лучше, чем передача неконстантной ссылкой в ​​любом случае, но я знаю, что это большая дискуссия.

О вашей SFINAE: SFINAE просто говорит, что если смена шаблона не удалась, компилятор будет продолжать искать совпадение, а не сразу бросать ошибку. Но это вызовет ошибку, если не найдет соответствия вообще. В вашем случае, если, например, T = C2, он все равно попытается скомпилировать handle_C1 (C2), который терпит неудачу. (Ваш "случай" - это решение во время выполнения, тогда как компилятор принимает эти решения во время компиляции)

Ответ 5

Вы можете вернуться к добрым старым указательным наложениям. Разрешено указывать указатель на тип указателю на другой тип, это просто UB, чтобы отменить указатель, если указанное значение не имеет правильного типа.

Но здесь вы знаете, что во время выполнения тип будет правильным, потому что std::is_same гарантирует его. Поэтому я бы написал:

template<typename T>
void handle(T &content){

    // Initialization was done here
    if (std::is_same<C1, T>()) {
        C1* c = reinterpret_cast<C1*>(&content);
       handle_C1(*c);
    }
    if (std::is_same<C2, T>()) {
        C2* c = reinterpret_cast<C2*>(&content);
       handle_C2(*c);
    }
}

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

Ответ 6

Здесь другое решение, которое просто извлекает код, общий для обоих типов поведения, в его собственную функцию:

class C1 { /* ... */ };
class C2 { /* ... */ };

template<typename T>
void handle_init(T& content) {
    // Common initialization code
}

void handle(C1& c1) {
    handle_init(c1);
    // C1-specific code
}

void handle(C2& c2) {
    handle_init(c2);
    // C2-specific code
}

В зависимости от кода внутри handle_init шаблон может не понадобиться, если, например, тело функции вызывает только методы "getter" content, которые могут быть вызваны заранее и переданы в handle_init функциями handle.

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

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

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