Reputation:
I have the two functions that are almost the same (with the exception that one of them is a template):
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);
}
Than, they are use like this:
foo(nullptr);
bar(nullptr);
The second one compiles and returns (int*)nullptr
, but the first one doesn't (in Visual Studio 2019 using C++17 giving the error foo: no matching overload found
). Why? Why does making this function a template cause it to cease to compile?
Using foo
like below doesn't help either, so the inability to deduce Args
is probably not the problem:
foo<>(nullptr);
In contrary, the following does work:
foo(std::variant<int*, std::tuple<>>(nullptr));
Is it possible to somehow avoid the need to write this in such a long manner?
Upvotes: 4
Views: 121
Reputation: 56068
I would avoid this construct simply because the rules about exactly how the compiler will (if it even does it at all) resolve the overload are so confusing that I couldn't really tell you what it did without looking at a standards document, and code like that should be avoided. I would force the overload resolution you want this way instead:
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)});
}
Upvotes: 0
Reputation: 96791
Apparently if a type of a function parameter depends on a template parameter that has to be deduced (because it's not specified in <...>
), then implicit conversions don't apply when passing an argument to that parameter.
The function parameters that do not participate in template argument deduction (e.g. if the corresponding template arguments are explicitly specified) are subject to implicit conversions to the type of the corresponding function parameter (as in the usual overload resolution).
A template parameter pack that is explicitly specified may be extended by template argument deduction if there are additional arguments:
template<class ... Types> void f(Types ... values); void g() { f<int*, float*>(0, 0, 0); // Types = {int*, float*, int} }
This also explains why foo<>(nullptr);
still doesn't work. Since the compiler tries to deduce additional types to extend Args
, in this case there doesn't seem to be any difference between foo(nullptr);
and foo<>(nullptr);
.
Upvotes: 3
Reputation: 12968
When a template function is considered it will only work for an exact match of the argument types at the call. This means that no conversions will be made (except for cv qualifiers).
A simple workaround in your case would be to make a function catch std::nullptr_t
and forward that to your template.
int* foo(std::nullptr_t) {
return foo(std::variant<int*, std::tuple<>>{nullptr});
}
Upvotes: 1