Шаблон функции с необязательным аргументом или перегружен
Я получил класс, содержащий 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
вместо.