0

I have a builder class that implements a template <typename M, typename ... Args> register_builder method. This method takes a string and a bunch of arguments and passes it to a const auto& factory = [&]() { return std::make_unique<M>(name, std::forward<Args>(args)...);}; that is used to generate some objects.

Depending on the optimization flag I pass to g++ or clang (14 and 15) this function builds the correct object or not.

With (-std=c++17), the output of the code snippet is correct: A 0 -3.14 while with (-std=c++17 -O3) the output is wrong: A 1000 2.79

It looks to me like only the last given variadic arguments are taken into account, but I can't see why.

#include <string>
#include <functional>
#include <memory>
#include <map>
#include <iostream>

struct iObj{
    using ptr_t = std::unique_ptr<iObj>;

    iObj(const std::string& name, const int& id, const double& x):_name(name), _id(id), _x(x)  {};
    virtual ~iObj(){};

    virtual void run() = 0;

    std::string _name;
    int _id;
    double _x;
};

struct myObj : iObj {
    myObj(const std::string& name, const int& id, const double& x) : iObj(name, id, x) {};
    void run() override {
         std::cout << _name << " " <<_id << " " << _x;
    };
};

struct builder 
{
using factory_t = std::function<iObj::ptr_t()>;

template <typename M, typename ... Args>
void register_builder(const std::string &name, Args &&...args)
{
  if (_factories.find(name) != _factories.end())
    return;

  const auto& factory = [&]() {
    return std::make_unique<M>(name, std::forward<Args>(args)...);
  };
  _factories[name] = std::move(factory);
}

iObj::ptr_t build(const std::string& name) const
{
    const auto& it =  _factories.find(name);
    return it->second();
}

std::map<std::string, factory_t> _factories;

};

int main()
{
 builder b;
 b.register_builder<myObj>("A", 0, -3.14);
 b.register_builder<myObj>("B", 1000, 2.79);

 auto obj = b.build("A");
 obj->run();

 return 0;
}

This is reproducible on Linux and on godbolt.org on gcc12.2.

François Andrieux
  • 28,148
  • 6
  • 56
  • 87
mehe
  • 15
  • 4
  • You capture the arguments by reference in the lambda. By the time you leave the register function they are invalidated. – François Andrieux Sep 03 '22 at 19:04
  • https://godbolt.org/z/8KM6GTjde – n. m. could be an AI Sep 03 '22 at 19:33
  • Yeap, the problem was capturing the args. I did try to capture them by value `[=]` but that did not work so I defaulted to by reference, hoping for the best. It seems there is another way, one that seems to fix my issue: `const auto& factory = [name, args = std::make_tuple(std::forward(args) ...)] () mutable{ return std::apply([=](auto ... args){ return std::make_unique(name, std::forward(args)...); }, std::move(args)); };` – mehe Sep 03 '22 at 19:55
  • Using `[=]` [works for me](https://godbolt.org/z/a858s9a47) but I had to remove the inner `std::forward` for it to compile. You should also be able to move initialize the captured values to leverage *some* perfect forwarding. – François Andrieux Sep 03 '22 at 20:30
  • @mehe *"I did try to capture them by value `[=]` but that did not work"* -- Did not work how? This is probably the question you should have asked, as capturing by value is the answer to the question you did ask. – JaMiT Sep 03 '22 at 20:56
  • @FrançoisAndrieux @JaMiT when capturing by value [=] it was throwing a compile error at `std::forward<>` as you pointed out. My lack of understanding of the std::forward made me backtrack and default to `[&]`. – mehe Sep 04 '22 at 08:04

0 Answers0