Type deduction resullts in ambiguous call of overloaded function - c++

While mixing type deduction with overloading, I stumbled upon a behavior of type deduction for lambda functions that I find difficult to understand.
When compiling this program:
#include <functional>
#include <cstdlib>
int case_0(int const& x) {
return 2*x;
}
int case_1(int& x) {
x += 2;
return 2*x;
}
class Test {
public:
Test(int const n) : n(n) {}
int apply_and_increment(std::function<int(int const&)> f) {
n++;
return f(n);
}
int apply_and_increment(std::function<int(int&)> f) {
return f(n);
}
private:
int n;
};
int main() {
Test t(1);
auto f = [](int const& x) -> int {
return 3*x;
};
t.apply_and_increment(case_0); // Fails compilation
t.apply_and_increment(std::function<int(int const&)>(case_0)); // Succeeds
t.apply_and_increment(case_1); // Succeeds
t.apply_and_increment(f); // Fails compilation
return EXIT_SUCCESS;
}
The output of the compilation is:
$ g++ -std=c++20 different_coonstness.cpp -o test
different_coonstness.cpp: In function ‘int main()’:
different_coonstness.cpp:34:30: error: call of overloaded ‘apply_and_increment(int (&)(const int&))’ is ambiguous
34 | t.apply_and_increment(case_0);
| ^
different_coonstness.cpp:16:7: note: candidate: ‘int Test::apply_and_increment(std::function<int(const int&)>)’
16 | int apply_and_increment(std::function<int(int const&)> f) {
| ^~~~~~~~~~~~~~~~~~~
different_coonstness.cpp:20:7: note: candidate: ‘int Test::apply_and_increment(std::function<int(int&)>)’
20 | int apply_and_increment(std::function<int(int&)> f) {
| ^~~~~~~~~~~~~~~~~~~
different_coonstness.cpp:37:25: error: call of overloaded ‘apply_and_increment(main()::<lambda(const int&)>&)’ is ambiguous
37 | t.apply_and_increment(f);
| ^
different_coonstness.cpp:16:7: note: candidate: ‘int Test::apply_and_increment(std::function<int(const int&)>)’
16 | int apply_and_increment(std::function<int(int const&)> f) {
| ^~~~~~~~~~~~~~~~~~~
different_coonstness.cpp:20:7: note: candidate: ‘int Test::apply_and_increment(std::function<int(int&)>)’
20 | int apply_and_increment(std::function<int(int&)> f) {
| ^~~~~~~~~~~~~~~~~~~
As far as I understand:
case_0 is ambiguous because there are 2 valid type conversions, std::function<int(const int&)> and std::function<int(int&)>, and both overloaded functions apply_and_increment() can be applied. This is why the explicit type conversion std::function<int(int const&)>(case_0) is required.
in case_1, the only valid conversion is std::function<int(int&)>, so there is no ambiguity.
I am not very familiar with type deduction and lambdas, so I am a bit surprised that t.apply_and_increment(f) fails to compile. I would expect that the type of the function would be deduced by the type signature, [](int const& x) -> int, in the lambda function.
Why is not f of type std::function<int(int const&)>?

Your understanding of overload resolution for case_0 and case_1 is correct:
A reference-to-non-const can be assigned to a reference-to-const, hence case_0() is callable from both of the std::function types being used, thus overload resolution is ambiguous when an implicit conversion is used, requiring you to specify the desired std::function type explicitly.
A reference-to-const cannot be assigned to a reference-to-non-const, hence case_1() is not callable from std::function<int(int const&)>, only from std::function<int(int&)>, thus overload resolution is not ambiguous when an implicit conversion is used.
A standalone function is not itself a std::function object, but can be assigned to a compatible std::function object.
Likewise, a lambda is not itself a std::function object, it is an instance of a compiler-defined functor type, which can be assigned to a compatible std::function object.
In both cases, std::function acts as a proxy, passing its own parameters to the function/lambda's parameters, and then returning whatever the function/lambda returns.
So, overload resolution fails for both case_0 and f for the exact same reason. When the compiler has to implicitly convert case_0/f into a std::function object, the conversion is ambiguous because case_0/f is callable from both of the std::function types being used.

Related

Implicit conversion of initializer lists and perfect forwarding

I'm trying to make perfect forwarding work with initializer lists. For the sake of the example, I'd like to have a variadic function that calls into another function, and still enjoy automatic conversion of initializer lists of the latter:
#include <iostream>
#include <vector>
void hello(std::string const& text, std::vector<int> const& test)
{
std::cout << "hello " << text << " " << test.size() << std::endl;
}
template<class ... Args>
void f(Args&& ... args)
{
return hello(std::forward<Args>(args)...);
}
int main()
{
hello("world", {1,2,3}); // WORKS
f("world", std::vector<int>({1,2,3})); // WORKS
f("world", {1,2,3}); // COMPILER ERROR
}
The error is
example.cpp: In function ‘int main()’:
example.cpp:21:21: error: too many arguments to function ‘void f(Args&& ...) [with Args = {}]’
21 | f("world", {1,2,3});
| ^
example.cpp:12:6: note: declared here
12 | void f(Args&& ... args)
| ^
example.cpp: In instantiation of ‘void f(Args&& ...) [with Args = {}]’:
example.cpp:21:21: required from here
example.cpp:14:15: error: too few arguments to function ‘void hello(const string&, const std::vector<int>&)’
14 | return hello(std::forward<Args>(args)...);
| ~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
example.cpp:6:6: note: declared here
6 | void hello(std::string const& text, std::vector<int> const& test)
| ^~~~~
Am I making any obvious mistake here?
The compiler is not able to recognize the type you are sending in the third case.
If you use
f("world", std::initializer_list<int>{1,2,3});
everything works.
This post has some detailed explanation and quotes the relevant part of the standard. It is for a slightly different case but the explanation still applies.
The problem is that the {1, 2, 3} argument to your second call to the templated f function is not sufficiently 'specific' for the compiler to unambiguously deduce its type in template substitution.
Explicitly defining that argument's type will resolve the issue:
f("world", std::initializer_list<int>{ 1, 2, 3 });
A very similar case is given (as an example of an error) on this cppreference page.

rvalue reference forwarding

I am writing a wrapper around std::jthread and some surrounding infrastructure. I cannot wrap my head around why the following won't compile:
#include <iostream>
#include <map>
#include <functional>
#include <thread>
// two random functions
void foo(int i) { std::cout << "foo " << i << std::endl; }
void bar(int i) { std::cout << "bar " << i << std::endl; }
// mechanism to identify them
enum function_kind {
foo_kind, bar_kind
};
std::map<function_kind, std::function<void(
int)>> kind_to_function{{foo_kind, foo},
{bar_kind, bar}};
// wrapper around jthread
// (additional functionality ommitted for brevity)
template<typename Callable, typename... Args>
class MyThread {
public:
explicit MyThread(Callable &&function, Args &&...args) : m_thread{
std::forward<Callable>(function),
std::forward<Args>(args)...} {}
private:
std::jthread m_thread;
};
int main() {
std::jthread t1(kind_to_function[foo_kind], 3); // works
MyThread t2(kind_to_function[foo_kind], 3); // complains
return 0;
}
I am really just trying to mimic whatever std::jthread is doing with my own class.
The IDE (clion) complains, that the first argument to t2 is not an rvalue. The compiler complains a little more complicated:
main.cpp: In function ‘int main()’:
main.cpp:29:46: error: class template argument deduction failed:
29 | MyThread t2(kind_to_function[foo_kind], 3); // complains
| ^
main.cpp:29:46: error: no matching function for call to ‘MyThread(std::map<function_kind, std::function<void(int)> >::mapped_type&, int)’
main.cpp:20:14: note: candidate: ‘MyThread(Callable&&, Args&& ...)-> MyThread<Callable, Args> [with Callable = std::function<void(int)>; Args = {int}]’ (near match)
20 | explicit MyThread(Callable &&function, Args &&...args) : m_thread{std::forward<Callable>(function),
| ^~~~~~~~
main.cpp:20:14: note: conversion of argument 1 would be ill-formed:
main.cpp:29:46: error: cannot bind rvalue reference of type ‘std::function<void(int)>&&’ to lvalue of type ‘std::map<function_kind, std::function<void(int)> >::mapped_type’ {aka ‘std::function<void(int)>’}
29 | MyThread t2(kind_to_function[foo_kind], 3); // complains
| ^
main.cpp:18:7: note: candidate: ‘template<class Callable, class ... Args> MyThread(MyThread<Callable, Args>)-> MyThread<Callable, Args>’
18 | class MyThread {
| ^~~~~~~~
main.cpp:18:7: note: template argument deduction/substitution failed:
main.cpp:29:46: note: ‘std::function<void(int)>’ is not derived from ‘MyThread<Callable, Args>’
29 | MyThread t2(kind_to_function[foo_kind], 3); // complains
| ^
In any case, the arguments work for std::jthread, which also just takes rvalues... So what am I missing?
The parameters of the MyThread constructor are not forwarding references because the constructor is not a template. Do not make the class a template, but only the constructor:
class MyThread {
public:
template<typename Callable, typename... Args>
explicit MyThread(Callable &&function, Args &&...args) :
m_thread{
std::forward<Callable>(function),
std::forward<Args>(args)...} {}
private:
std::jthread m_thread;
};
In any case, the arguments work for std::jthread, which also just takes rvalues... So what am I missing?
jthread is not a template, its constructor is a template. Which makes the rvalue references to template parameters into forwarding references, not plain rvalue references.
However, since MyThread is itself a template, and its constructor is not a template constructor, the behavior is not the same. After instantiation, it's a regular constructor that accepts only rvalues.
Forwarding references are contingent on template argument deduction happening for the function template they are a part of. So a non-template constructor means no forwarding references.
Okay, but you didn't specify template arguments to MyThread, why was there seemingly no error? Because class template argument deduction allows you to omit those. And CTAD happens in its own overload resolution step, completely disjoint from actually choosing a constructor to initialize the object. One step can be ill-formed while the other is not.

std::bind on a generic lambda - auto type deduction

Consider the following code:
#include <iostream>
#include <functional>
int main() {
auto run = [](auto&& f, auto&& arg) {
f(std::forward<decltype(arg)>(arg));
};
auto foo = [](int &x) {};
int var;
auto run_foo = std::bind(run, foo, var);
run_foo();
return 0;
}
Which gives the following compilation error when compiled with clang:
$ clang++ -std=c++14 my_test.cpp
my_test.cpp:6:9: error: no matching function for call to object of type 'const (lambda at my_test.cpp:8:16)'
f(std::forward<decltype(arg)>(arg));
^
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/6.3.1/../../../../include/c++/6.3.1/functional:998:14: note: in instantiation of function template specialization 'main()::(anonymous class)::operator()<const (lambda at my_test.cpp:8:16) &, const int &>' requested here
= decltype( std::declval<typename enable_if<(sizeof...(_Args) >= 0),
^
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/6.3.1/../../../../include/c++/6.3.1/functional:1003:2: note: in instantiation of default argument for 'operator()<>' required here
operator()(_Args&&... __args) const
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
my_test.cpp:11:12: note: while substituting deduced template arguments into function template 'operator()' [with _Args = <>, _Result = (no value)]
run_foo();
^
my_test.cpp:8:16: note: candidate function not viable: 1st argument ('const int') would lose const qualifier
auto foo = [](int &x) {};
^
my_test.cpp:8:16: note: conversion candidate of type 'void (*)(int &)'
1 error generated.
Why is arg deduced to be const int& instead of just int&?
std::bind documentation says:
Given an object g obtained from an earlier call to bind, when it is
invoked in a function call expression g(u1, u2, ... uM), an invocation
of the stored object takes place, as if by std::invoke(fd,
std::forward(v1), std::forward(v2), ...,
std::forward(vN)), where fd is a value of type std::decay_t the
values and types of the bound arguments v1, v2, ..., vN are determined
as specified below.
...
Otherwise, the
ordinary stored argument arg is passed to the invokable object as
lvalue argument: the argument vn in the std::invoke call above is
simply arg and the corresponding type Vn is T cv &, where cv is the
same cv-qualification as that of g.
But in this case, run_foo is cv-unqualified. What am I missing?
MWE:
#include <functional>
int main() {
int i;
std::bind([] (auto& x) {x = 1;}, i)();
}
[func.bind]/(10.4) states that the cv-qualifiers of the argument passed to the lambda are those of the argument to bind, augmented by the cv-qualifiers of the call wrapper; but there are none, and thus a non-const int should be passed in.
Both libc++ and libstdc++ fail to resolve the call. For libc++, reported as #32856, libstdc++ as #80564. The main problem is that both libraries infer the return type in the signature somehow, looking like this for libstdc++:
// Call as const
template<typename... _Args, typename _Result
= decltype( std::declval<typename enable_if<(sizeof...(_Args) >= 0),
typename add_const<_Functor>::type&>::type>()(
_Mu<_Bound_args>()( std::declval<const _Bound_args&>(),
std::declval<tuple<_Args...>&>() )... ) )>
_Result operator()(_Args&&... __args) const
During template argument deduction as necessitated by overload resolution, the default template argument will be instantiated, which causes a hard error due to our ill-formed assignment inside the closure.
This can be fixed by perhaps a deduced placeholder: remove _Result and its default argument entirely, and declare the return type as decltype(auto). This way, we also get rid of SFINAE which influences overload resolution and thereby induces incorrect behaviour:
#include <functional>
#include <type_traits>
struct A {
template <typename T>
std::enable_if_t<std::is_const<T>{}> operator()(T&) const;
};
int main() {
int i;
std::bind(A{}, i)();
}
This should not compile—as explained above, the argument passed to A::operator() should be non-const because i and the forwarding call wrapper are. However, again, this compiles under libc++ and libstdc++, because their operator()s fall back on const versions after the non-const ones fail under SFINAE.

deduce of argument of type class method (overloads by const qualifier) fails with trailing return type in gcc, but not in clang

Nothing clearer than an old good MCVE:
struct X {
auto get(int) const -> int { return {}; }
auto get(int) -> int { return {}; }
};
template <class R> auto f(auto (X::*)(int) const -> R) {}
// ^~~~ ~~~~
// trailing return type
int main() {
f(&X::get);
}
This fails in g++ (4.9.2 & 5.1.0). However if the old return type is used:
template <class R> auto f(R (X::*)(int) const) {}
// ^
// old return type
it works.
On clang (3.5.0) both variants work.
I know that trailing return type changes when the return type is inferred and the scope of it, so I wouldn't be quick to cast it as a gcc bug. So what does the standard says? Which compiler is right?
The most significant message in the error I think is
couldn't deduce template parameter ‘R’`
g++ full message:
main2.cpp: In function ‘int main()’:
main2.cpp:21:12: error: no matching function for call to ‘f(<unresolved overloaded function type>)’
f(&X::get);
^
main2.cpp:18:25: note: candidate: template<class R, class auto:1> auto f(auto:1 (X::*)(int) const)
template <class R> auto f(auto (X::*)(int) const -> R) {}
^
main2.cpp:18:25: note: template argument deduction/substitution failed:
main2.cpp:21:12: note: types ‘auto:1 (X::)(int) const’ and ‘int (X::)(int)’ have incompatible cv-qualifiers
f(&X::get);
^
main2.cpp:21:12: note: couldn't deduce template parameter ‘R’
<builtin>: recipe for target 'main2' failed
make: *** [main2] Error 1
As pointed in the question this is a gcc bug which was beed fixed in version 6
gcc.gnu.org/bugzilla/show_bug.cgi?id=69139

no viable overloaded '=' for overloaded static member functions

I have this simplified code consisting of a class with a static function, which is stored in map:
#include <iostream>
#include <functional>
#include <map>
class A {
public:
static void f(const std::string &s) { std::cout << s; }
};
std::map<std::string, std::function<void(std::string const &)>> fs;
int main() {
fs["f"] = &A::f;
fs["f"]("hello");
}
This prints the expected hello.
The problem occurs if I overload f() with:
static void f(const std::string &s, int c) { while(c-->0) { std::cout << s; } }
This results in the error:
error: no viable overloaded '='
fs["f"] = &A::f;
~~~~~~~ ^ ~~~~~
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.9/../../../../include/c++/4.9/functional:2241:7: note: candidate function not viable: no overload of 'f' matching 'const std::function<void (const std::basic_string<char> &)>' for 1st argument
operator=(const function& __x)
^
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.9/../../../../include/c++/4.9/functional:2259:7: note: candidate function not viable: no overload of 'f' matching 'std::function<void (const std::basic_string<char> &)>' for 1st argument
operator=(function&& __x)
^
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.9/../../../../include/c++/4.9/functional:2273:7: note: candidate function not viable: no overload of 'f' matching 'nullptr_t' for 1st argument
operator=(nullptr_t)
^
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.9/../../../../include/c++/4.9/functional:2302:2: note: candidate template ignored: couldn't infer template argument '_Functor'
operator=(_Functor&& __f)
^
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.9/../../../../include/c++/4.9/functional:2311:2: note: candidate template ignored: couldn't infer template argument '_Functor'
operator=(reference_wrapper<_Functor> __f) noexcept
^
However, calling both functions works:
A::f("hello ");
A::f("echo ", 3);
So, my question are:
Why this code not compiling even though the operator= seems to exist and function if I don't overload f()?
How can I get it to work without giving both functions different names?
Why this code not compiling even though the operator= seems to exist
and function if I don't overload f()?
Because the compiler doesn't know which overload to choose. How could he? There is no criterion upon which he can decide which one is suited better. Every std::function allows arbitrary function objects to be assigned and doesn't check any signatures. If you wanted to save only function pointers of this particular signature you should have declared the map appropriately.
How can I get it to work without giving both functions different
names?
As already mentioned it works by casting the expression to a function pointer of the specific type.
fs["f"] = static_cast<void(*)(std::string const&)>( &A::f );
This way no ambiguities arise; There is exactly one overload that can be casted to this function to pointer type. If this appears more often then a typedef could be feasible.
Or a little helper class template:
template <typename... Exact>
struct funptr
{
template <typename R>
constexpr auto operator()(R(*p)(Exact...)) -> decltype(p)
{ return p; }
};
fs["f"] = funptr<std::string const&>()(&A::f);
Demo.