The following code example represents a simplified version of my code.
The code snippet contains a class called Command that I can construct using named constructors. When constructing the class, it requires a string that I would like to parse by reference to limit stack usage.
My production code contain many different types of the Command class that all derives from a base (omitted here for simplicity), so I have also created a templated 'instantiate' function that hides some of the std::shared_ptr boiler-plate code.
The problem I face is, that when using the templated instantiate function with a reference to a std::string, the compiler is unable to deduce the template parameters and complains about inconsistent parameter pack. In the example main function I included calling the named constructor directly with a string reference, and also an example that calls an alternative named constructor that takes just a string.
I would like to avoid the named constructor that takes a string instance, so can anyone explain to me why the named constructor that takes a string reference fails when encapsulating it in a templated function?
#include <memory>
#include <string>
class Command
{
public:
static Command* named_constructor_str_ref(const std::string &t)
{
return new Command(t);
}
static Command* named_constructor(const std::string t)
{
return new Command(t);
}
~Command() = default;
private:
explicit Command(const std::string &t) : text(t) {}
const std::string text;
};
template <typename T, typename... Ts>
std::shared_ptr<Command> instantiate(T *(*named_ctr)(Ts...), Ts... args)
{
if(named_ctr != nullptr)
{
return std::shared_ptr<T>(named_ctr(args...));
}
return nullptr;
}
int main(void)
{
const std::string s = "My text";
// Calling named constructor with string reference directly works
auto c1 = std::shared_ptr<Command>(Command::named_constructor_str_ref(s));
// Instantiating with no string reference works
auto c2 = instantiate(Command::named_constructor, s);
// Instantiating with string reference does not work... Why????
auto c3 = instantiate(Command::named_constructor_str_ref, s);
return 0;
}
The error message from gcc
<source>: In function 'int main()':
<source>:45:26: error: no matching function for call to 'instantiate(Command* (&)(const std::string&), const std::string&)'
45 | auto c3 = instantiate(Command::named_constructor_str_ref, s);
| ~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:24:26: note: candidate: 'template<class T, class ... Ts> std::shared_ptr<Command> instantiate(T* (*)(Ts ...), Ts ...)'
24 | std::shared_ptr<Command> instantiate(T *(*named_ctr)(Ts...), Ts... args)
| ^~~~~~~~~~~
<source>:24:26: note: template argument deduction/substitution failed:
<source>:45:26: note: inconsistent parameter pack deduction with 'const std::__cxx11::basic_string<char>&' and 'std::__cxx11::basic_string<char>'
45 | auto c3 = instantiate(Command::named_constructor_str_ref, s);
| ~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In
template <typename T, typename... Ts>
std::shared_ptr<Command> instantiate(T *(*named_ctr)(Ts...), Ts... args)
Ts...
is deduced from 2 places:
T *(*named_ctr)(Ts...)
Ts...args
With instantiate(Command::named_constructor_str_ref, s)
Ts...
is deduced as const std::string&
from the former, and std::string
from the latter, so mismatching deduction, so a fail.
(It is the meaning of gcc error "inconsistent parameter pack deduction with")
You might
template <typename T, typename... Ts, typename... Us>
std::shared_ptr<Command> instantiate(T *(*named_ctr)(Ts...), Us&&... args)
{
if(named_ctr != nullptr)
{
return std::shared_ptr<T>(named_ctr(std::forward<Us>(args)...));
}
return nullptr;
}
// or
template <typename Func, typename... Ts>
std::shared_ptr<Command> instantiate(Func f, Ts&&... args)
{
if (f)
{
return std::shared_ptr<std::remove_pointer_t<decltype(f(std::forward<Ts>(args)...))>>(f(std::forward<Ts>(args)...));
}
return nullptr;
}
template <typename T, typename... Ts>
std::shared_ptr<Command> instantiate(T *(*named_ctr)(Ts...), std::type_identity_t<Ts>... args)
{
if(named_ctr != nullptr)
{
return std::shared_ptr<T>(named_ctr(std::forward<Ts>(args)...));
}
return nullptr;
}