std::bind with variadic template member function and universal references - c++

Here i have small piece of code and it compiles and works just fine
(at least with my GCC 7.3.0 and Ubuntu 18.04):
#include <functional>
#include <string>
#include <iostream>
void func(int a, const std::string& b, const std::string& c)
{
std::cout << a << b << c << std::endl;
}
class Test
{
public:
template <typename ... ARGS>
bool func_to_bind(ARGS&& ... args) const {
func(args...);
return true;
}
template <typename ... ARGS>
void binding_func(ARGS&& ... args) const
{
auto func_obj = std::bind(&Test::func_to_bind<int&, ARGS&...>, this, 42, args...);
func_obj();
}
};
int main()
{
Test obj;
obj.binding_func(std::string("one"), std::string("two"));
}
The part that i don't understand is this line:
std::bind(&Test::func_to_bind<int&, ARGS&...>, this, 42, args...);
Why does compiler require to use references as template type parameters?
If i remove reference from int like this:
std::bind(&Test::func_to_bind<int, ARGS&...>, this, 42, args...);
It won't compile. Also if i change func_to_bind signature to this:
bool func_to_bind(ARGS& ... args) const
It will compile just fine even with missing reference.
Could anyone explain what's exactly going on here?
I also did some search and found this question:
How to combine std::bind(), variadic templates, and perfect forwarding?
But i don't completely understand the answer.

If you specify the template argument as int explicitly, then the parameter type of func_to_bind would become int&&, i.e. an rvalue-reference type. Note that the stored arguments are passed to the invokable object as lvalues by std::bind:
Otherwise, the ordinary stored argument arg is passed to the invokable object as lvalue argument:
The lvalue can't be bound to the rvalue-referece parameter then invocation fails.
If you specify the template argument as int& explicitly, then the parameter type of func_to_bind becomes int&, i.e. an lvalue-reference type; lvalue could be bound to lvalue-reference then it works fine.
And if you change the parameter type of func_to_bind to ARGS&, it'll be always an lvalue-reference, for the same reason above it'll work fine.

Related

type deduction for std::function argument types with auto adds const

I have a struct with a method called call which has a const overload. The one and only argument is a std::function which either takes a int reference or a const int reference, depending on the overload.
The genericCall method does exactly the same thing but uses a template parameter instead of a std::function as type.
struct SomeStruct {
int someMember = 666;
void call(std::function<void(int&)> f) & {
f(someMember);
std::cout << "call: non const\n";
}
void call(std::function<void(const int&)> f) const& {
f(someMember);
std::cout << "call: const\n";
}
template <typename Functor>
void genericCall(Functor f) & {
f(someMember);
std::cout << "genericCall: non const\n";
}
template <typename Functor>
void genericCall(Functor f) const& {
f(someMember);
std::cout << "genericCall: const\n";
}
};
When I now create this struct and call call with a lambda and auto & as argument the std::function always deduces a const int & despite the object not being const.
The genericCall on the other hand deduces the argument correctly as int & inside the lamdba.
SomeStruct some;
some.call([](auto& i) {
i++; // ?? why does auto deduce it as const int & ??
});
some.genericCall([](auto& i) {
i++; // auto deduces it correctly as int &
});
I have no the slightest clue why auto behaves in those two cases differently or why std::function seems to prefer to make the argument const here. This causes a compile error despite the correct method is called. When I change the argument from auto & to int & everything works fine again.
some.call([](int& i) {
i++;
});
When I do the same call with a const version of the struct everything is deduced as expected. Both call and genericCall deduce a const int & here.
const SomeStruct constSome;
constSome.call([](auto& i) {
// auto deduces correctly const int & and therefore it should
// not compile
i++;
});
constSome.genericCall([](auto& i) {
// auto deduces correctly const int & and therefore it should
// not compile
i++;
});
If someone could shine some light on this I would be very grateful!
For the more curious ones who want to dive even deeper, this problem arose in the pull request: https://github.com/eclipse-iceoryx/iceoryx/pull/1324 while implementing a functional interface for an expected implementation.
The issue is that it's a hard error to try to determine whether your lambda is Callable with const int & returning void, which is needed to determine whether you can construct a std::function<void(const int&)>.
You need to instantiate the body of the lambda to determine the return type. That's not in the immediate context of substituting a template argument, so it's not SFINAE.
Here's an equivalent error instantiating a trait.
As #aschepler notes in the comments, specifying a return type removes the need to instantiate the body of your lambda.
The problem is that generic lambdas (auto param) are equivalent to a callable object whose operator() is templated. This means that the actual type of the lambda argument is not contained in the lambda, and only deduced when the lambda is invoked.
However in your case, by having specific std::function arguments, you force a conversion to a concrete type before the lambda is invoked, so there is no way to deduce the auto type from anything. There is no SFINAE in a non-template context.
With no specific argument type, both your call are valid overloads. Actually any std::function that can match an [](auto&) is valid. Now the only rule is probably that the most cv-qualified overload wins. You can try with a volatile float& and you will see it will still choose that. Once it choose this overload, the compilation will fail when trying to invoke.

Why does template argument deduction failed with variadic template parameters of a std::function callback?

Let's consider the following functions:
// run_cb_1(): Explicitly defined prototype
void run_cb_1(const std::function<void(int)> & callback, int p)
{
callback(p);
}
// run_cb_2(): One template parameter
template <typename T>
void run_cb_2(const std::function<void(T)> & callback, const T & t)
{
callback(t);
}
// run_cb_3(): Variable number of template parameters
template <typename ... Args>
void run_cb_3(const std::function<void(Args...)> & callback, const Args & ... args)
{
callback(args...);
}
Now if I want to use these functions as follows:
int main()
{
auto f = [](int a){
std::cout << a << '\n';
};
run_cb_1(f, 5); // OK
run_cb_2(f, 5); // KO --> I understand why
run_cb_2<int>(f, 5); // OK
run_cb_3(f, 5); // KO --> I understand why
run_cb_3<int>(f, 5); // KO --> I don't understand why...
return 0;
}
I get a "no matching function call" with run_cb_2() and run_cb_3() while it works perfectly fine with run_cb_1().
I think it behaves as expected because I did not provided the type for the template argument (since it can not be deduced trivially as it is for run_cb_1()).
But specifying the template type solves the problem for run_cb_2() (as I would expect) but not for run_cb_3().
I know I can solve it either by explicitly declaring f as:
std::function<void(int)> f = [](int a){
std::cout << a << '\n';
};
or by passing f as:
run_cb_2(std::function<void(int)>(f), 5);
run_cb_3(std::function<void(int)>(f), 5);
My question is: Why does the template argument deduction fail with run_cb_3() (with variadic template parameters) even when explicitly specifying the template type(s) ?
It is obvious that I missed something (maybe basic) but I don't know what it is.
Any help will be appreciated.
The reason this fails is because there isn't just one type the compiler can use. When you do
run_cb_2<int>(f, 5);
The compiler looks at run_cb_2 and sees that there is only one template parameter. Since you've provided that it skips the deduction phase and stamps out run_cb_2<int>.
With
run_cb_3<int>(f, 5);
You're in a different boat. run_cb_3 has a variadic template parameter which means just supplying int is not enough to skip the deduction. You specified the first argument, but there could be more so it goes into the argument deduction phase to figure it out. That means it checks callback to make sure what it deduces there matches what it deduces for args. Since the lambda is not a std::function it can't deduce Args... from it. Once that happens the compiler stops and issues an error.
With run_cb_3<int>, you don't explicitly provide full Args..., just the first type; it might have other.
It is used for example in function such as std::make_unique:
template <class T, class... Args>
std::unique_ptr<T> make_unique(Args&&... args);
and
std::make_unique<MyObj>(var1, var2); // T = MyObj
// Args... = [decltype((var1)), decltype((var2))]
Extra args are deduced from argument.
To force evaluation, you might use:
(&run_cb_3<int>)(f, 5); // OK
Demo

Passing template arguments as target type

In this shortened example (not real world code), I'm attempting to call Callback with an int &, however, when going via the CallMethod method, the template parameter is interpreted as an int, meaning it can't convert it to the target parameter type.
Is this possible? I know I can cast the parameter to the correct type when calling CallMethod, however I'd like the solution to be implicit if possible.
#include <functional>
#include <iostream>
using namespace std;
void Callback(int &value)
{
value = 42;
}
template <typename Method, typename ...Params>
void CallMethod(Method method, Params ...params)
{
method(std::forward<Params>(params)...);
}
int main()
{
int value = 0;
CallMethod(&Callback, value);
cout << "Value: " << value << endl;
return 0;
}
You aren't correctly forwarding your arguments. In order to make use of perfect-forwarding std::forward should operate on forwarding references, which are when you have an rvalue reference in a deduced context. Your CallMethod function should look like this:
template <typename Method, typename ...Params>
void CallMethod(Method method, Params&& ...params)
{
method(std::forward<Params>(params)...);
}
Demo

How to filter const types and non const types using meta programing?

I have this code
#include <iostream>
size_t F()
{
return 0;
}
template <class Type, class... NextTypes>
size_t F(const Type& type, const NextTypes&... nextTypes)
{
if (!std::is_const<Type>::value)
return sizeof(type) + F(nextTypes...);
else
return F(nextTypes...);
}
int main()
{
int a = 0;
const int b = 0;
const size_t size = F(a,b);
std::cout << "size = " << size << std::endl;
return 0;
}
I'm trying to know in compilation time the total size of constant parameters and non const parameters. The current out put is 8, for some reason the compiler thinks b is not constant, I used typeid and decltype to print the types of a and b and indeed the output shows b is an int and not const int as I expected. What am I missing? Is it possible to separate a variadic set of arguments to const arguments and non const?
Consider this function template:
template<typename T>
void deduce(const T&);
If you let the compiler deduce a type for T from an argument expression, the deduced type will never be const: It will try to make the const T of the function parameter identical to the type of the argument expression used to call the function. For example:
struct cls {};
const cls c;
deduce(c) // deduces T == cls
By deducing T == cls, the compiler succeeds in making const T identical to the argument type const cls. There is no reason for the compiler to produce two different functions for const- and non-const argument types; the parameter type of the function template instantiation will be const-qualified in any case: you requested it by saying const T& instead of, say, T&.
You can deduce the const-ness of an argument by not cv-qualifying the function parameter:
template<typename T>
void deduce(T&);
However, this will fail to bind to non-const temporaries (rvalues). To support them as well, you can use universal references:
template<typename T>
void deduce(T&&);
This will deduce an lvalue-reference type for T if the argument is an lvalue, and no reference if the argument is an rvalue. The const-ness will be deduced correctly.
For example, if the argument has the type const A and is an lvalue, T will be deduced to const A&. The function parameter then is const A& &&, which is collapsed to const A& (an lvalue-reference). If the argument is an rvalue, T will be deduced to const A, and the function parameter becomes const A&& (an rvalue-reference).
Note that since T can be a reference in this case, you need to remove that before checking for const-ness: std::is_const< typename std::remove_reference<T>::type >::value.

Variadic templates with 'const' parameter overloading

Reduced sample code:
#include <iostream>
template<typename T>
void func(T &x)
{
std::cout << "non-const " << x << std::endl;
}
template<typename T>
void func(const T &x)
{
std::cout << "const " << x << std::endl;
}
template<typename ...ARGS>
void proxy(ARGS ...args)
{
func(args...);
}
int main()
{
int i = 3;
func(i);
func(5);
func("blah");
proxy(i);
proxy(5);
proxy("blah");
}
Expected output:
non-const 3
const 5
const blah
non-const 3
const 5
const blah
Actual output:
non-const 3
const 5
const blah
non-const 3
non-const 5
non-const blah
So somehow the const qualifier of the function parameter gets lost when put through the variadic template. Why? How can I prevent this?
PS: tested with GCC 4.5.1 and SUSE 11.4
You just stumble upon the forwarding problem. This issue is solved using perfect forwarding.
Basically, you need to take your parameters by rvalue-reference, and rely on std::forward to correctly forward them while keeping their nature:
template<typename ...Args>
void proxy(Args&& ...args)
{
func(std::forward<Args>(args)...);
}
As Luc already mentioned this is a problem of forwarding and the answer to how to prevent it is to use perfect forwarding. But I will try to address the other questions at the end:
So somehow the const qualifier of the function parameter gets lost when put through the variadic template. Why?
This has everything to do with type inference. Ignore that you are using variadic templates and consider the simplest one argument template:
template <typename T>
void one_arg_proxy( T arg ) {
func( arg );
}
At the place of call you have one_arg_proxy( 5 ), that is, the argument is an int rvalue. Type inference kicks in to figure out what the type T should be, and the rules dictate that T is int, so the call gets translated to one_arg_proxy<int>(5) and the instantiation of the template that gets compiled is:
template <>
void one_arg_proxy<int>( int arg ) {
func( arg );
}
Now the call to func takes an lvalue argument, and thus the version of func taking a non-const reference is a better match (no conversions required) than the one taking a const&, yielding the result that you are getting. The issue here is that func does not get called with the argument to proxy, but rather with the internal copy that proxy made of it.