Шаблон функции с необязательным аргументом или перегружен

Я получил класс, содержащий 20 элементов структуры в классическом C-Array. Элементы от 0 до 5 принадлежат к типу A, от 6 до 15 - к типу B, а остальные принадлежат к типу C. Для создания цикла этих элементов я разработал три функциональных шаблона. Вот очень простой пример моей проблемы (я знаю, это не имеет смысла, но это только демонстрирует то, что я хочу):

#include <iostream>
#include <string>

struct MyStruct {
    int Value;

MyStruct() {
    this->Value = 0;
}

MyStruct(int fValue) {
    this->Value = fValue;
}

void PrintValue() { std::cout << "Value: " << std::to_string(this->Value) << std::endl; }
};

class MyClass {
private:
    struct MyStruct valArr[20];
    int aRange = 5;
    int bRange = 10;

public:
    MyClass() {
        for (int i = 0; i < 20; i++) {
            valArr[i] = MyStruct(i);
        }
}

template<typename FUNCTION>
inline void LoopRangeA(FUNCTION f, bool GetIndex = false) {
        for (int i = 0; i < aRange; i++) {
            f(&this->valArr[i]);
        }
}

template<typename FUNCTION>
inline void LoopRangeB(FUNCTION f) {
    for (int i = aRange; i < bRange; i++) {
        f(&this->valArr[i]);
    }
}

template<typename FUNCTION>
inline void LoopRangeC(FUNCTION f) {
    for (int i = bRange; i < 20; i++) {
        f(&this->valArr[i]);
    }
}

template<typename FUNCTION>
inline void LoopAll(FUNCTION f) {
    for (int i = 0; i < 20; i++) {
        f(&this->valArr[i]);
    }
}
};

int main() {
MyClass Cls = MyClass();

Cls.LoopRangeA([](MyStruct* pStr) {pStr->PrintValue(); });

    std::cout << "Application is finished. Press ENTER to exit..." << std::endl;
std::cin.get();
}

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

Я пытался это было, но это не работает (просто показать разницу):

    template<typename FUNCTION>
inline void LoopRangeA(FUNCTION f, bool GetIndex = false) {
    if (GetIndex) {
        for (int i = 0; i < aRange; i++) {
            f(&this->valArr[i], i);
        }
    }else {
        for (int i = 0; i < aRange; i++) {
            f(&this->valArr[i]);
        }
    }
}

int main() {
    MyClass Cls = MyClass();

    Cls.LoopRangeA([](MyStruct* pStr, int& i) {std::cout << "Index: " << std::to_string(i); pStr->PrintValue(); std::cout << "" << std::endl; }, true);
    std::cout << "Application is finished. Press ENTER to exit..." << std::endl;
    std::cin.get();
}

У кого-нибудь есть идея, как решить эту проблему, не определяя завершенные новые члены функции?

Заранее спасибо, Ян

3 ответа

Если вы можете использовать constexpr, то в constexpr, если код оптимизирован во время компиляции, это означает, что если передано значение false, компилятор будет компилироваться напрямую, в противном случае, если это означает, что он не выдаст ошибку.

 if constexpr (GETINDEX){
    //do something f(par1,par2);
 }
 else{
   //or f(par1);
 }

Теперь, когда вы скомпилируете это, и GETINDEX будет ложным, f(par1,par2) не будет проверено, иначе будет скомпилировано. Это поможет вам вызвать функцию.

Ваша проблема в том, что компилятор должен знать, какую ветвь условия использовать во время компиляции, потому что функции имеют разные сигнатуры. Таким образом, у вас есть либо решение, заданное @TrebuchetMS, т.е. принимайте только функции с индексом. Или вы должны как-то выразить свои намерения в системе типов.

Я вижу три возможных решения, но, вероятно, есть еще:

1) перегрузка LoopRangeA для обоих типов функций, как это:

inline void LoopRangeA(void (*f)(MyStruct*)) {
    for (int i = 0; i < aRange; i++) {
        f(&this->valArr[i]);
    }
}

inline void LoopRangeA(void (*f)(MyStruct*, size_t)) {
    for (int i = 0; i < aRange; i++) {
        f(&this->valArr[i], i);
    }
}

Это позволит выбрать тип цикла в зависимости от сигнатуры функции. Недостатком является то, что вам, вероятно, потребуется обеспечить перегрузки для onst Mystruct* тоже.

2) Учитывая, что вы можете использовать C++17, вы можете использовать if constexpr предоставляя bool параметр шаблона:

template<bool GetIndex, typename FUNCTION>
void LoopRangeA1(FUNCTION f) {
    if constexpr(GetIndex) {
        for (int i = 0; i < aRange; i++) {
            f(&this->valArr[i], i);
        }
    } else {
        for (int i = 0; i < aRange; i++) {
            f(&this->valArr[i]);
        }
    }
}

Но, как @StoryTeller упоминает в своем комментарии, вы должны передавать избыточную информацию, так как в любом случае потребность в индексе закодирована в сигнатуре функции.

3) Поэтому я бы предпочел 3-е решение, которое использует лучшее из обоих:

Сначала вы предоставляете функцию, которая определяет способность использовать индекс во время компиляции. это нуждается в небольшом обмане constexpr:

constexpr std::false_type eats_index(...) { return {}; }

template<typename T>
constexpr auto eats_index(T) -> decltype(std::declval<T>()(std::declval<MyStruct*>(), 0), std::true_type{}) {
    return {};
}

Затем вы реализуете свою функцию следующим образом:

template<typename FUNCTION>
void LoopRangeA2(FUNCTION f) {
    if constexpr(eats_index(f)) {
        for (int i = 0; i < aRange; i++) {
            f(&this->valArr[i], i);
        }
    } else {
        for (int i = 0; i < aRange; i++) {
            f(&this->valArr[i]);
        }
    }
}

Наконец, это main функция, показывающая, как использовать решения:

int main() {
    MyClass Cls = MyClass();

    auto print_no_idx = [](MyStruct* pStr) {pStr->PrintValue(); };
    auto print_const_no_idx = [](const MyStruct* pStr) { };
    auto print_with_idx = [](MyStruct* pStr, size_t idx) {
        std::cout << "index: " << idx << " -> ";
        pStr->PrintValue();  };

    Cls.LoopRangeA(print_no_idx);
    Cls.LoopRangeA(print_const_no_idx); // <- does not compile, you'd need another overload
    Cls.LoopRangeA(print_with_idx);

    Cls.LoopRangeA1<false>(print_no_idx);
    Cls.LoopRangeA1<false>(print_const_no_idx); // <- works w/o additional overload
    Cls.LoopRangeA1<true>(print_with_idx);

    static_assert(!eats_index(print_no_idx));
    static_assert(eats_index(print_with_idx));

    Cls.LoopRangeA2(print_no_idx);
    Cls.LoopRangeA2(print_const_no_idx); // <- works, w/o additional overload
    Cls.LoopRangeA2(print_with_idx);




    std::cout << "Application is finished. Press ENTER to exit..." << std::endl;
    std::cin.get();
}

Смотрите здесь для полного примера.

Запуск вашего кода получил мне эту ошибку:

In instantiation of 'void MyClass::LoopRangeA(FUNCTION, bool) [with FUNCTION = main()::<lambda(MyStruct*, int)>]':
46:14: error: no match for call to '(main()::<lambda(MyStruct*, int)>) (MyStruct*)'
             f(&this->valArr[i]);
             ~^~~~~~~~~~~~~~~~~~

Так что я подозревала, что это связано с твоим else дело:

template<typename FUNCTION>
inline void LoopRangeA(FUNCTION f, bool GetIndex = false) {
    if (GetIndex) {
        for (int i = 0; i < aRange; i++) {
            f(&this->valArr[i], i);
        }
    }else {
        for (int i = 0; i < aRange; i++) {
            f(&this->valArr[i]);            //   <-- this guy over here
        }
    }
}

Важный бит информации, предоставляемой выводом ошибки:

FUNCTION = main()::<lambda(MyStruct*, int)>

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

[&](MyStruct* pStr, int i = -1) {...}

или для вызова вашей функции:

else {
    for (int i = 0; i < aRange; i++) {
        f(&this->valArr[i], -1);       //  -1
    }
}

Ваш код работает нормально после этого.

Но используя -1 может быть слишком хакерским ("магические числа" и все такое), поэтому я мог бы выбрать std::optional вместо.

Другие вопросы по тегам