Шаблон функции с необязательным аргументом или перегружен - PullRequest
0 голосов
/ 13 января 2019

Я получил класс, содержащий 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();
}

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

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

Ответы [ 3 ]

0 голосов
/ 13 января 2019

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

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.

0 голосов
/ 13 января 2019

Ваша проблема в том, что компилятору необходимо знать, какую ветвь условия использовать во время компиляции, потому что функции имеют разные сигнатуры. Таким образом, у вас есть либо решение, данное @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();
}

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

0 голосов
/ 13 января 2019

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

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

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

...