0

I got a class, containing 20 structure elements in a classical C-Array. The elements form 0 to 5 belong to Type A, from 6 to 15 they belong to Type B and the rest belongs to Type C. For looping this elements, I designed three function templates. Here is a very simple example of my problem (I know, it makes no sense, butit only demonstrates what I want):

#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();
}

Well, that runs well. But sometimes, I also need the array-index of the element. As I still have lots of this function templates in my real programm, I try to avoid defining new functions but want to overload them or use an optional argument.

I tried this was, but it doesn't run (just show the difference):

    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();
}

Does anybody has an idea, how to solve that problem without defining complette new function members?

Thank you in advance, Jan

Jan021981
  • 521
  • 3
  • 28
  • Side note: with streams, you don't necessarily need to convert an `int` to `std::string` to write to the string. `std::cout << std::to_string(i)` could be simplified to `std::cout << i`. – TrebledJ Jan 13 '19 at 12:40
  • Are you loop function supposed to be member functions, how else are they accessing `aRange` and `bRange`? What does *it doesn't run* mean... compile error, wrong result, something else? – super Jan 13 '19 at 12:48
  • yes, the loop function is part of my class containing the private member aRange and bRange. – Jan021981 Jan 13 '19 at 13:12
  • 1
    Can't you just pass the index *always*? The called functor can choose to ignore it if doesn't need it. No need to complicate things with redundant flags. – StoryTeller - Unslander Monica Jan 13 '19 at 13:14

3 Answers3

0

If you can use constexpr then in constexpr if the code is optimized at compile time that means if false is passed compiler will directly compile else not if that means it wont give error.

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

now when u will compile this and GETINDEX is false f(par1,par2) wont get checked and else will be compiled. This will help you to call function.

0

Running your code got me this error:

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]);
             ~^~~~~~~~~~~~~~~~~~

So I suspected it had to do with your else case:

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
        }
    }
}

The important bit of information the error output provides is:

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

Since (I'm guessing) when the function template was evaluated, the template should work on all calls and instances, regardless whether or not they'd be executed. Providing a default argument to your lambda fixed it:

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

or to your function call:

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

Your code runs fine afterwards.

But using -1 may be a bit too hacky ("magic numbers" and all that), so I might opt for std::optional instead.

TrebledJ
  • 8,713
  • 7
  • 26
  • 48
0

Your problem is, that the compiler needs to know which branch of the conditional to use at compile-time, because the functions have different signatures. Thus you have either the solution given by @TrebuchetMS, i.e. accept only functions with index. Or you have to somehow express your intents in the type system.

I see three possible solutions but there are probably more:

1) Overload LoopRangeA for both types of functions like this:

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);
    }
}

This will select the kind of loop depending on the function signature. The drawback is, that you'll probably need to provide overloads for onst Mystruct* too.

2) Given you can use C++17, you could leverage if constexpr by providing a bool template parameter:

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]);
        }
    }
}

But, as @StoryTeller mentions in his / her comment, you have to pass redundant information, as the need for an index is encoded in the function signature anyways.

3) Therefore, I'd prefer the 3rd solution which takes the best of both others:

First you provide a function that determines the ability to eat an index at compile time. this needs some little constexpr-trickery:

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 {};
}

Then you implement your function like this:

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]);
        }
    }
}

Finally, this is the main function showing how to use the solutions:

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();
}

See here for a full example.

florestan
  • 4,405
  • 2
  • 14
  • 28