以下代码示例代表我的代码的简化版本。
代码片段包含一个名为 Command 的类,我可以使用命名构造函数来构造该类。构造类时,它需要一个字符串,我想通过引用来解析该字符串以限制堆栈使用。
我的生产代码包含许多不同类型的 Command 类,它们全部派生自基类(为简单起见,此处省略),因此我还创建了一个模板化“实例化”函数,该函数隐藏了一些 std::shared_ptr 样板代码。
我面临的问题是,当使用模板化实例化函数并引用 std::string 时,编译器无法推导模板参数并抱怨参数包不一致。 在示例主函数中,我包括使用字符串引用直接调用命名构造函数,还有一个调用仅采用字符串的替代命名构造函数的示例。
我想避免采用字符串实例的命名构造函数,所以任何人都可以向我解释为什么采用字符串引用的命名构造函数在将其封装在模板化函数中时会失败吗?
#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;
}
来自 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);
| ~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
在
template <typename T, typename... Ts>
std::shared_ptr<Command> instantiate(T *(*named_ctr)(Ts...), Ts... args)
Ts...
是从2个地方推导出来的:
T *(*named_ctr)(Ts...)
Ts...args
与
instantiate(Command::named_constructor_str_ref, s)
Ts... 前者推导为
const std::string&
,后者推导为std::string
,所以推导不匹配,所以失败。
你可能
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;
}