Function overloading with the std :: function argument: why was the const method never called?

#include <functional> #include <iostream> #include <string> #include <vector> using namespace std; class A { public: void doStuff(function<void (const string *)> func) const { cout << "Const method called" << endl; for(const auto& i_string : m_vec) func(i_string); } void doStuff(function<void (string *)> func) { cout << "Non-const method called" << endl; doStuff([&func](const string *str) { auto mutableString = const_cast<string *>(str); func(mutableString); }); } private: vector<string *> m_vec; }; int main() { auto a = A{}; a.doStuff([](string *str){ *str = "I modified this string"; }); } 

In this example, the const method is never called. If the code looks weird, here is what I'm trying to do:

Instead of the getter method, I let clients iterate over objects by passing a function. To enable both const and non-constant access, I want to provide const and non-const overloads. Also, to avoid copying and pasting, I want to implement the non-const method in terms of the const method: the const method in my code is actually more complex than the one I use here.

Now my questions are: if you run this code, it will recursively call the non-const function until the stack overflows. I don’t understand why the string doStuff([&func](const string *str) in the non-constant method calls itself and not the const method.

+5
source share
5 answers

A non-constant method is declared as a receiving function that can be called with argument string * . The provided function accepts const string * . string * implicitly converted to const string* . Therefore, a function with const string * is a valid argument for the non-const method, and the non-const method is chosen because this also is not const const.

Use const_cast on this to use the const method:

 const_cast<const A*>(this)->doStuff(…); 
+6
source

An overload of const is only called when an instance of class A has a value of const . This is due to const at the end of the declaration:

 void doStuff(function<void (const string *)> func) const //This const at the end causes //The member function to only be //called when `a` is const 

The qualifier at the end applies to the this object, not to the parameters. Removing const will lead to ambiguity, so use the StenSoft method to make it work.

+1
source

I don't think this is the correct overload:

 void doStuff(function<void (const string *)> func) const 

and

 void doStuff(function<void (string *)> func) 

Overloaded functions must have the same prototype, but different arguments. In your case, your const method can only be called if your object is const . Here you have only two methods that can be called in different situations, but these situations are not caused by the overloading mechanism or any other function dependent on the argument.

Also, why not let people use your object with default iterators? Add begin() and end() methods to your object, and let people do whatever they want without your interface, but the std lib interface: they can use ranking, some algorithms like find and find_if , as well as good things.

+1
source

Metaprogramming pattern:

 template<template<class...>class Z, class always_void, class...Ts> struct can_apply_helper:std::false_type{}; template<template<class...>class Z, class...Ts> struct can_apply_helper<Z, decltype((void)(Z<Ts...>)), Ts...>:std::true_type{}; template<template<class...>class Z, class...Ts> using can_apply=can_apply_helper<Z,void,Ts...>; 

A value that determines whether the type expression will be a valid call:

 // result_of_t fails to be SFINAE in too many compilers: template<class F, class...Ts> using invoke_helper_t=decltype( std::declval<F>()(std::declval<Ts>()...) ); template<class Sig> struct can_invoke; template<class F, class...Ts> struct can_invoke<F(Ts...)>: can_apply<invoke_helper_t, F, Ts...> {}; 

Now replace doStuff in your class. The first determines whether the function can be called using std::string const* :

 template<class F, class=std::enable_if_t< can_invoke< F&(std::string const*) >{} >> void doStuff(F&& func) const { cout << "Const method called" << endl; for(const auto& i_string : m_vec) func(i_string); } 

This one detects if you cannot call it with std::string const* and that you can call it std::string* :

 template<class F, class=std::enable_if_t< !can_invoke< F&(std::string const*) >{} && can_invoke< F&(std::string*) >{} >> void doStuff(F&& func) { cout << "Non-const method called" << endl; doStuff([&func](const string *str) { auto mutableString = const_cast<string *>(str); func(mutableString); }); } 

this also removes the unnecessary erasure of type std::function in your example and directs any call that can go to the const method to the const method.

Aside, std::vector<std::string*> storage is almost always bad.

+1
source

Perhaps you think that a lambda with the signature void(const string *) cannot be called using string * and therefore not converted to function<void (string *)> . This is not true since string * implicitly converted to const string * , and therefore it is completely legal to construct a function<void (string *)> object from a function object that expects const string * . This is only a facial case that is not allowed. This means that the compiler determines that both argument conversions are viable (with equal rank). This would make the two candidates ambiguous in overload resolution, but since the implicit this pointer is not const , the non- const overload is preferred (the rank of the implicit this is an "exact match", for non-t28> versus the "conversion" for const ).

The solution, as mentioned earlier, ensures that the implicit this point is equal to const . This will remove the non- const overload from the candidate set and cause the intended overload to be called.

+1
source

All Articles