我有两个几乎相同的功能(除了其中一个是模板):
int* bar(const std::variant<int*, std::tuple<float, double>>& t)
{
return std::get<0>(t);
}
template <typename... Args>
int* foo(const std::variant<int*, std::tuple<Args...>>& t)
{
return std::get<0>(t);
}
然后,它们的用法如下:
foo(nullptr);
bar(nullptr);
第二个编译并返回(int*)nullptr
,但第一个不编译(在Visual Studio 2019中,使用C ++ 17给出错误foo: no matching overload found
)。为什么?为什么将此函数用作模板会导致其停止编译?
像下面一样使用foo
也无济于事,因此无法推断Args
可能不是问题:
foo<>(nullptr);
相反,以下方法确实有效:
foo(std::variant<int*, std::tuple<>>(nullptr));
是否有可能以某种方式避免写这么长时间?
答案 0 :(得分:4)
显然,如果函数参数的类型取决于必须推导的模板参数(因为未在<...>
中指定),则在将参数传递给该参数时,隐式转换将不适用。 / p>
不参与模板参数的函数参数 推论(例如,如果相应的模板参数明确 指定)进行隐式转换为 相应的功能参数(如通常的重载 分辨率)。
可以扩展明确指定的模板参数包 通过模板参数推导(如果还有其他参数):
template<class ... Types> void f(Types ... values); void g() { f<int*, float*>(0, 0, 0); // Types = {int*, float*, int} }
这也解释了为什么foo<>(nullptr);
仍然不起作用。由于编译器会尝试推断出其他类型来扩展Args
,因此在这种情况下,foo(nullptr);
和foo<>(nullptr);
之间似乎没有任何区别。
答案 1 :(得分:2)
当考虑使用模板函数时,它将仅在调用时与参数类型完全匹配时才起作用。这意味着将不进行任何转换(cv限定词除外)。
一种简单的解决方法是使函数捕获std::nullptr_t
并将其转发到模板。
int* foo(std::nullptr_t) {
return foo(std::variant<int*, std::tuple<>>{nullptr});
}
答案 2 :(得分:1)
我之所以会避免这种构造,仅仅是因为关于编译器将如何解决过载的规则(如果可以做到的话)完全令人困惑,以至于我不看标准文档就无法真正告诉你它做了什么,应避免使用类似的代码。我会改用这种方式强制您想要的重载分辨率:
template <typename... Args>
int *foo(const ::std::variant<int*, ::std::tuple<Args...>> &t)
{
return ::std::get<0>(t);
}
int *foo(int *ip)
{
using my_variant = ::std::variant<int *, ::std::tuple<>>;
return foo(my_variant{ip});
}
template <typename... Args>
int *foo(::std::tuple<Args...> const &t)
{
using my_variant = ::std::variant<int *, ::std::tuple<Args...>>;
return foo(my_variant{t});
}
template <typename... Args>
int *foo(::std::tuple<Args...> &&t)
{
using my_variant = ::std::variant<int *, ::std::tuple<Args...>>;
return foo(my_variant{::std::move(t)});
}