Strange inconsistency between function template and "normal" function - c++

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?

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.
Source:
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);.

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

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

Related

When are template parameter packs deduced as empty?

Consider the following examples (Coliru link):
template <class... T> struct S { using type = int; };
template <class... T>
void f(typename S<T...>::type) {
static_assert(sizeof...(T) == 0);
}
template <class... T>
void g(typename S<T...>::type, S<T...>*) {}
int main() {
f(42);
g(42, nullptr);
}
GCC and Clang are both happy with the call to f, but not the call to g.
In the call to f, although T... appears in a non-deduced context, it ends up being deduced as empty. This seems to be due to [temp.arg.explicit]/4:
... A trailing template parameter pack ([temp.variadic]) not otherwise deduced will be deduced as an empty sequence of template arguments. ...
In the call to g, however, the fact that T... additionally appears in a deduced context, which causes deduction to be attempted and failed, seems to cause g to become non-viable. There seems to be no "fallback" to T... being empty once deduction has been attempted and failed.
Is this behaviour intended? If so, why?
If so, was it intended that the "not otherwise deduced" wording specifies this behaviour? (i.e., it implies that the empty fallback only occurs if the pack appears in no deduced contexts)
If so, is this wording clear enough? It seems a plausible alternative reading of "not otherwise deduced" is "either deduction was not done, or deduction was attempted and failed".
... A trailing template parameter pack ([temp.variadic]) not otherwise
deduced will be deduced as an empty sequence of template arguments.
...
It could be enough to say that the not otherwise deduced is not a clause that should somehow or automagically relax some other rules or actually it is has nothing to do with why it's malformed (contrary to what I think you imply).
This other rule is perhaps best demonstrated by another very simple example:
template<class T>
void f(T, T){};
int main() {
f(int{42},short{42});
}
The above fails to compile. Why? Because even while short converts to int seamlessly (promotion), it is not the same type.
Additionally since nullptr just has the somewhat plain type of std::nullptr_t - it is very ill-suited to participate in template argument deduction at all.
So let's forget about non-deduced context for a moment, and try with deduced one:
template <class... T>
void g(S<T...>*, S<T...>* ) {}
int main() {
S<> s1;
g(&s1, nullptr);
}
or if you prefer, just
int main() {
S<> s1;
g(&s1, 0);
}
and both fail for the same reason.
Now, if you would like to allow conversion - then use an identity template - and this even works for the non-deduced context!
For your case, the example could look like (c++2a):
template <class... T>
void g(typename S<T...>::type, std::type_identity_t<S<T...> >*) {}
int main() {
f(42);
g(42, nullptr);
}
Which is valid. (note if you don't have have c++20 just write the identity template yourself)
As stated in a comment, turning the question around could perhaps lead to a more interesting question ?
What's the reasoning for allowing empty template argument deduction in non-deduced contexts?

Why are variadic templates different than non-variadic, for only one argument?

This code compiles just fine:
template <typename T1>
struct Struct {
};
struct ConvertsToStruct {
operator Struct<int>() const;
};
template <typename T>
void NonVariadicFunc(Struct<T>);
int main() {
NonVariadicFunc<int>(ConvertsToStruct{});
return 0;
}
But an attempt to make it a little more generic, by using variadic templates, fails to compile:
template <typename T1>
struct Struct {
};
struct ConvertsToStruct {
operator Struct<int>() const;
};
template <typename... T>
void VariadicFunc(Struct<T...>);
int main() {
VariadicFunc<int>(ConvertsToStruct{});
return 0;
}
What's going wrong? Why isn't my attempt to explicitly specify VariadicFunc's template type succeeding?
Godbolt link => https://godbolt.org/g/kq9d7L
There are 2 reasons to explain why this code can't compile.
The first is, the template parameter of a template function can be partially specified:
template<class U, class V> void foo(V v) {}
int main() {
foo<double>(12);
}
This code works, because you specify the first template parameter U and let the compiler determine the second parameter. For the same reason, your VariadicFunc<int>(ConvertsToStruct{}); also requires template argument deduction. Here is a similar example, it compiles:
template<class... U> void bar(U... u) {}
int main() {
bar<int>(12.0, 13.4f);
}
Now we know compiler needs to do deduction for your code, then comes the second part: compiler processes different stages in a fixed order:
cppreference
Template argument deduction takes place after the function template name lookup (which may involve argument-dependent lookup) and before template argument substitution (which may involve SFINAE) and overload resolution.
Implicit conversion takes place at overload resolution, after template argument deduction. Thus in your case, the existence of a user-defined conversion operator has no effect when compiler is doing template argument deduction. Obviously ConvertsToStruct itself cannot match anything, thus deduction failed and the code can't compile.
The problem is that with
VariadicFunc<int>(ConvertsToStruct{});
you fix only the first template parameter in the list T....
And the compiler doesn't know how to deduce the remaining.
Even weirder, I can take the address of the function, and then it works
It's because with (&VariadicFunc<int>) you ask for the pointer of the function (without asking the compiler to deduce the types from the argument) so the <int> part fix all template parameters.
When you pass the ConvertToStruct{} part
(&VariadicFunc<int>)(ConvertToStruct{});
the compiler know that T... is int and look if can obtain a Struct<int> from a ConvertToStruct and find the apposite conversion operator.

Implicit template argument is invalid, but compiles anyway

I have the following class:
class FunctionCallback
{
public:
static CallbackHandle Create(const std::function<void(void)> &function);
template<typename T,typename... TARGS>
static CallbackHandle Create(const std::function<T(TARGS...)> &function);
};
I then call 'Create' like this:
FunctionCallback::Create<void,float>([](float f) -> void {}); // Whether I use a lambda-function or a function pointer makes no difference
Even though that should be correct (?), visual studio underlines that line in read with the message:
Error: no instance of overloaded function "FunctionCallback::Create" matches the argument list
argument types are: (lambda []void (float f)->void)
However the program compiles fine in visual studio without any warnings or errors.
g++-5 is unable to compile it altogether with a similar message.
When changing it to:
FunctionCallback::Create<void,float>(std::function<void(float)>([](float f) -> void {}));
It doesn't display the message anymore and compiles both on windows and linux.
Why can't it deduce the type properly unless I explicitly specify it?
You almost never want to deduce the type of a type-eraser, that std::function is an example of. With a few exceptions, template type deduction attempts to deduce the exact type of an argument. For a std::function<T>-type parameter this means that the compiler can deduce T if the corresponding argument is a std::function as well. The type of a lambda expression is not a std::function.
Even if you explicitly specify type template arguments, the deduction still continues after corresponding types have been substituted with the arguments. In the case of a parameter pack, the compiler still tries to deduce the rest of arguments, so for FunctionCallback::Create<void,float> the compiler ends up with:
template <typename... TARGS>
static CallbackHandle Create(std::function<void(float, TARGS...)> function);
and this won't match anything else than std::function<void(float, TARGS...)> (or something that derives from this type); otherwise the deduction for TARGS fails.
If your goal is to always specify the parameter pack's types explicitly, you can put that pack (or the entire parameter) in a non-deduced context:
template <typename T> struct identity { using type = T; };
template <typename T> using identity_t = typename identity<T>::type;
struct FunctionCallback
{
template <typename T, typename... TARGS>
static void Create(identity_t<std::function<T(TARGS...)>> function);
};
FunctionCallback::Create<void, float>([](float f) -> void {});
DEMO
In this case, however, you'll have to pay the price of type-erasure. Instead, you can consider accepting any function object type:, letting the compiler to deduce the exact type of the argument:
struct FunctionCallback
{
template <typename F>
static void Create(F function){}
};
FunctionCallback::Create([](float f) -> void {});
DEMO 2

SFINAE and the address of an overloaded function

I'm experimenting with resolving the address of an overloaded function (bar) in the context of another function's parameter (foo1/foo2).
struct Baz {};
int bar() { return 0; }
float bar(int) { return 0.0f; }
void bar(Baz *) {}
void foo1(void (&)(Baz *)) {}
template <class T, class D>
auto foo2(D *d) -> void_t<decltype(d(std::declval<T*>()))> {}
int main() {
foo1(bar); // Works
foo2<Baz>(bar); // Fails
}
There's no trouble with foo1, which specifies bar's type explicitly.
However, foo2, which disable itself via SFINAE for all but one version of bar, fails to compile with the following message :
main.cpp:19:5: fatal error: no matching function for call to 'foo2'
foo2<Baz>(bar); // Fails
^~~~~~~~~
main.cpp:15:6: note: candidate template ignored: couldn't infer template argument 'D'
auto foo2(D *d) -> void_t<decltype(d(std::declval<T*>()))> {}
^
1 error generated.
It is my understanding that C++ cannot resolve the overloaded function's address and perform template argument deduction at the same time.
Is that the cause ? Is there a way to make foo2<Baz>(bar); (or something similar) compile ?
As mentioned in the comments, [14.8.2.1/6] (working draft, deducing template arguments from a function call) rules in this case (emphasis mine):
When P is a function type, function pointer type, or pointer to member function type:
If the argument is an overload set containing one or more function templates, the parameter is treated as a non-deduced context.
If the argument is an overload set (not containing function templates), trial argument deduction is attempted using each of the members of the set. If deduction succeeds for only one of the overload set members, that member is used as the argument value for the deduction. If deduction succeeds for more than one member of the overload set the parameter is treated as a non-deduced context.
SFINAE takes its part to the game once the deduction is over, so it doesn't help to work around the standard's rules.
For further details, you can see the examples at the end of the bullet linked above.
About your last question:
Is there a way to make foo2<Baz>(bar); (or something similar) compile ?
Two possible alternatives:
If you don't want to modify the definition of foo2, you can invoke it as:
foo2<Baz>(static_cast<void(*)(Baz *)>(bar));
This way you explicitly pick a function out of the overload set.
If modifying foo2 is allowed, you can rewrite it as:
template <class T, class R>
auto foo2(R(*d)(T*)) {}
It's more or less what you had before, no decltype in this case and a return type you can freely ignore.
Actually you don't need to use any SFINAE'd function to do that, deduction is enough.
In this case foo2<Baz>(bar); is correctly resolved.
Some kind of the general answer is here: Expression SFINAE to overload on type of passed function pointer
For the practical case, there's no need to use type traits or decltype() - the good old overload resolution will select the most appropriate function for you and break it into 'arguments' and 'return type'. Just enumerate all possible calling conventions
// Common functions
template <class T, typename R> void foo2(R(*)(T*)) {}
// Different calling conventions
#ifdef _W64
template <class T, typename R> void foo2(R(__vectorcall *)(T*)) {}
#else
template <class T, typename R> void foo2(R(__stdcall *)(T*)) {}
#endif
// Lambdas
template <class T, class D>
auto foo2(const D &d) -> void_t<decltype(d(std::declval<T*>()))> {}
It could be useful to wrap them in a templated structure
template<typename... T>
struct Foo2 {
// Common functions
template <typename R> static void foo2(R(*)(T*...)) {}
...
};
Zoo2<Baz>::foo2(bar);
Although, it will require more code for member functions as they have modifiers (const, volatile, &&)

Specifying default value for template function argument

Could you explain why the following code doesn't compile? An obvious workaround is to add a 1-argument overload of Apply, is there a simpler one?
template <typename T>
T Identity(const T& i_val)
{
return i_val;
}
template <typename Val, typename Fn>
Val Apply(const Val& v, Fn fn = Identity<Val>)
{
return fn(v);
}
int main() {
Apply(42); // error: no matching function for call to 'Apply(int)'
Apply(42, Identity<int>); // OK
return 0;
}
Template argument deduction doesn't work that way -- you can't deduce the type of an argument from a defaulted value. In C++11, you can however specify a default template argument:
template <typename Val, typename Fn = Val(&)(Val const &)>
Val Apply(const Val& v, Fn fn = Identity<Val>)
{
return fn(v);
}
Looking up the function to call consists of:
1. creating the set of candidates, which includes template argument deduction
2. determining the best overload
If I understand the standard correctly, only actual function arguments (i.e., not the default ones) take part in deducing the template arguments. Therefore from the argument 42, the only thing the compiler can infer is that Val = int. The overload does not enter the candidate set and the default argument is never looked at.
Apply is a templated function. You need to do Apply<MyValueType,MyFuncType>(42);
You could reasonably expect the compiler to infer Val to be int, but you can't expect it to infer the type of function, even though you've given a default parameter. As a result, it won't infer that you're trying to call that declaration of the Apply function.