具有可选参数或重载的功能模板

时间:2019-01-13 12:31:25

标签: c++ lambda function-templates

我有一个类,在一个经典的C数组中包含20个结构元素。形式从0到5的元素属于Type A,从6到15的元素属于Type B,其余元素属于TypeC。 为了循环这些元素,我设计了三个功能模板。这是我的问题的一个非常简单的示例(我知道,这没有任何意义,但仅演示了我想要的):

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

有人有一个主意,如何在不定义complette新函数成员的情况下解决该问题?

预先感谢您, 扬

3 个答案:

答案 0 :(得分:0)

如果可以使用constexpr,则在constexpr中,如果代码在编译时进行了优化,这意味着如果传递了false,则编译器将直接进行编译,否则,它将不会给出错误。

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

现在您何时将编译它,而GETINDEX为false f(par1,par2)将不被检查,否则将被编译。这将帮助您调用函数。

答案 1 :(得分:0)

运行您的代码使我收到此错误:

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

自从(我猜)函数模板被评估以来,该模板应可用于所有调用和实例,无论它们是否将被执行。为您的lambda提供默认参数可以解决该问题:

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

或转到您的函数调用:

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

您的代码此后运行正常。

但是使用-1可能有点太过分了(“魔术数字”等等),所以我可能会选择std::optional

答案 2 :(得分:0)

您的问题是,编译器需要知道在编译时使用条件语句的哪个分支,因为函数具有不同的签名。因此,您可以使用@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)因此,我更喜欢第三个解决方案,该解决方案兼具其他优点:

首先,您提供一个确定在编译时使用索引的功能的函数。这需要一些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 {};
}

然后您就可以像这样实现您的功能:

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

有关完整示例,请参见here