2

I am studying about lambda inheritance and I try to change the following example under this stackoverflow link. I modified the code using template deduction guide and fold expression.

The piece of code

#include <iostream>
using namespace std;

template<class... Ts>
class FunctionSequence :Ts...
{
    using Ts::operator()...;
};
template <class...Ts> FunctionSequence(Ts...) -> FunctionSequence<Ts...>; // (1)

int main(){

    //note: these lambda functions are bug ridden. Its just for simplicity here.
    //For correct version, see the one on coliru, read on.
    auto trimLeft = [](std::string& str) -> std::string& { str.erase(0, str.find_first_not_of(' ')); return str; };
    auto trimRight = [](std::string& str) -> std::string& { str.erase(str.find_last_not_of(' ')+1); return str; };
    auto capitalize = [](std::string& str) -> std::string& { for(auto& x : str) x = std::toupper(x); return str; };

    auto trimAndCapitalize = FunctionSequence{trimLeft, trimRight, capitalize};
    std::string str = " what a Hullabaloo     ";

    std::cout << "Before TrimAndCapitalize: str = \"" << str << "\"\n";
    trimAndCapitalize(str);
    std::cout << "After TrimAndCapitalize:  str = \"" << str << "\"\n";

    return 0;
}

However I get the following error

<source>: In function 'int main()':
<source>:19:78: error: no matching function for call to 'FunctionSequence<main()::<lambda(std::string&)>, main()::<lambda(std::string&)>, main()::<lambda(std::string&)> >::FunctionSequence(<brace-enclosed initializer list>)'
   19 |     auto trimAndCapitalize = FunctionSequence{trimLeft, trimRight, capitalize};
      |                                                                              ^
<source>:5:7: note: candidate: 'constexpr FunctionSequence<main()::<lambda(std::string&)>, main()::<lambda(std::string&)>, main()::<lambda(std::string&)> >::FunctionSequence(const FunctionSequence<main()::<lambda(std::string&)>, main()::<lambda(std::string&)>, main()::<lambda(std::string&)> >&)'
    5 | class FunctionSequence :Ts...
      |       ^~~~~~~~~~~~~~~~
<source>:5:7: note:   candidate expects 1 argument, 3 provided
<source>:5:7: note: candidate: 'constexpr FunctionSequence<main()::<lambda(std::string&)>, main()::<lambda(std::string&)>, main()::<lambda(std::string&)> >::FunctionSequence(FunctionSequence<main()::<lambda(std::string&)>, main()::<lambda(std::string&)>, main()::<lambda(std::string&)> >&&)'
<source>:5:7: note:   candidate expects 1 argument, 3 provided
ASM generation compiler returned: 1

What goes wrong?

Code example --> demo

getsoubl
  • 808
  • 10
  • 25

1 Answers1

2
using Ts::operator()...;

This "hoists" all the operator() from base classes into the class namespace.

That's apparently not what you want here. It looks like you want to perform the constituent base-class operations in sequence.

Here's how you could write that in a nifty way using the comma operator:

template<class... Ts>
struct FunctionSequence : Ts...  {
    auto operator()(std::string& str) const {
        return (Ts::operator()(str), ...);
    }
};

Live On Coliru

#include <iostream>
using namespace std;

template<class... Ts>
struct FunctionSequence : Ts...  {
    auto operator()(std::string& str) const {
        return (Ts::operator()(str), ...);
    }
};
template <class...Ts> FunctionSequence(Ts...) -> FunctionSequence<Ts...>; // (1)

int main(){
    //note: these lambda functions are bug ridden. Its just for simplicity
    //here.  For correct version, see the one on coliru, read on.
    auto trimLeft = [](std::string& str) -> std::string& { str.erase(0, str.find_first_not_of(' ')); return str; };
    auto trimRight = [](std::string& str) -> std::string& { str.erase(str.find_last_not_of(' ')+1); return str; };
    auto capitalize = [](std::string& str) -> std::string& { for(auto& x : str) x = std::toupper(x); return str; };

    auto trimAndCapitalize = FunctionSequence{trimLeft, trimRight, capitalize};
    std::string str = " what a Hullabaloo     ";

    std::cout << "Before TrimAndCapitalize: str = \"" << str << "\"\n";
    trimAndCapitalize(str);
    std::cout << "After TrimAndCapitalize:  str = \"" << str << "\"\n";

    return 0;
}

Prints

Before TrimAndCapitalize: str = " what a Hullabaloo     "
After TrimAndCapitalize:  str = "WHAT A HULLABALOO"

Notes

I would suggest inheriting privately, and making the functions either void-operations working on the argument, or make them pure (taking const-references and returning copies). Also, see function composition in C++ / C++11 e.g.

sehe
  • 374,641
  • 47
  • 450
  • 633
  • 2
    Note that you do `f(x), g(x), h(x)` whereas OP might want `h(g(f(x)))` (which does same result in OP example) – Jarod42 Dec 25 '20 at 15:11
  • How does this address the construction failure in the question? – Davis Herring Dec 25 '20 at 17:18
  • @DavisHerring the problem in the question wasn't construction failure, it was ambiguous `operator()` – sehe Dec 25 '20 at 20:33
  • @sehe: The question quotes a compiler error for the construction; perhaps that’s not reproducible and should be edited? – Davis Herring Dec 25 '20 at 20:35
  • @DavisHerring mmm yeah maybe the message OP copied was from different code, now that I read it closely. I just copied and observed the error with the code given – sehe Dec 25 '20 at 20:37