功能模板和“正常”功能之间的奇怪不一致

时间:2019-07-11 17:45:59

标签: c++ visual-c++ c++17

我有两个几乎相同的功能(除了其中一个是模板):

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));

是否有可能以某种方式避免写这么长时间?

3 个答案:

答案 0 :(得分:4)

显然,如果函数参数的类型取决于必须推导的模板参数(因为未在<...>中指定),则在将参数传递给该参数时,隐式转换将不适用。 / p>

Source:

  

不参与模板参数的函数参数   推论(例如,如果相应的模板参数明确   指定)进行隐式转换为   相应的功能参数(如通常的重载   分辨率)。

     

可以扩展明确指定的模板参数包   通过模板参数推导(如果还有其他参数):

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)});
}