2

I have been trying to capture some parameter pack parameters within std function lambda, in order to save functions in memory for future use.

However, in some cases, where any of the values from these captured parameters are modified after having been captured, the future use of this ones are not what was expected.

I would like to store in my std function an immutable copy of the parameter pack.

This code will be implemented as a library, so users could use it for savig some text for future printing. This way we cannot manage what parameters we recieve in the parameter pack. This example must be valid for strings, const char *, int, float, ...

Here it is an example code: Code link

template <typename... Args>
void doPrint(std::ostream& out, const Args &... args)
{
    using expander = int[];
    (void)expander{0, (void(out << args), 0)...};
}

template<typename... Args>
void printArgs(const Args & ... args)
{
    doPrint(std::cout, args...);
}

class PrintTest
{
private:

    std::vector<std::function<void()>> myFunctions;   

public:

    template<typename... Args>
    void saveText(const char * text, const Args & ... args)
    {
        std::function<void()> f = [this, text, args...]()
        {
            std::cout << text;
            printArgs(args ...);
            std::cout << std::endl;
        };

        this->myFunctions.push_back(f);
    }

    void printSavedTexts()
    {
        for(auto fun : this->myFunctions)
            fun();

        this->myFunctions.clear();
    }

};

int main()
{
    PrintTest test;

    {
        int four = 4;
        test.saveText(" Hello world ", 1, 2, 3, std::to_string(four).c_str());

        std::string a ="Just";
        const char * b = " be ";
        test.saveText(" Bye, Bye! ", a.c_str(), b, std::string("yourself!").c_str());
        a = "?";

        for(int i = 0; i <= 5; ++i)
        {
            std::string n = std::to_string(i);
            test.saveText("", n.c_str());
        }
    }

   test.printSavedTexts();
}

The output of this example is:

// Hello world 1234
// Bye, Bye! ? be yourself!
// 5
// 5
// 5
// 5
// 5
// 5

And it should be:

// Hello world 1234
// Bye, Bye! Just be yourself!
// 0
// 1
// 2
// 3
// 4
// 5

Is there a better way to save the text and the parameter pack received in memory for future use? Instead of using std function stored in vector.

Samuel
  • 816
  • 1
  • 7
  • 13
  • 1
    You may want to pass the string `test.saveText("", n);` itself. See https://godbolt.org/z/Yndsv9dT6 – Const Sep 14 '21 at 08:18
  • @Const This code will be implemented as a library, so users could use it for savig some text for future printing. This way we cannot manage what parameters we recieve in the parameter pack. This example must be valid for strings, const char *, int, float, ... – Samuel Sep 14 '21 at 08:50

1 Answers1

5

In test.saveText("", n.c_str());, you're passing a const char* pointer got from n to saveText, which is captured by the lambda at last. n is destroyed when get out of the for loop, left the pointers captured dangling. Deference on them leads to UB.

You can use std::string directly like test.saveText("", n);; i.e. remove all the .c_str()s.

LIVE

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • That is correct, thank you. However, this is just a simple example. I am trying to implement this code as a library that must support all the possible argument types, not just std string. – Samuel Sep 14 '21 at 08:37
  • 2
    @Samuel Then you have to be careful for raw pointers; make sure they're still valid when used in lambdas. – songyuanyao Sep 14 '21 at 08:39
  • Is there any way to manage raw pointers received inside parameter pack? Or a different way to save the received parameter pack in memory for future use, than using std function? – Samuel Sep 14 '21 at 14:43
  • 1
    @Samuel It's not easy to handle raw pointers specially in parameter pack; I still think it's caller's responsibility to keep the pointer valid, or pass `std::string` or smart pointers from the beginning. – songyuanyao Sep 14 '21 at 15:15