#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";
});
}
在此示例中,永远不会调用const方法。如果代码看起来很奇怪,这就是我要做的事情:
我让客户端通过传递函数来迭代对象,而不是getter方法。要启用const和非const访问,我想提供const和非const重载。此外,为了避免复制和paste,我想用const方法实现非const方法:我的代码中的const方法实际上比我在这里使用的方法更复杂。
现在,我的问题是:如果运行此代码,它将递归调用非const函数,直到堆栈溢出。我不明白为什么非const方法中的行doStuff([&func](const string *str)
会调用自身而不是const方法。
答案 0 :(得分:5)
非const方法被声明为可以使用string *
参数调用的接受函数。提供的函数接受const string *
。 string *
可以隐含地转换为const string*
。因此,const string *
的函数是非const方法的可接受参数,并且选择了非const方法,因为this
也是非常量的。
在const_cast
上使用this
来使用const方法:
const_cast<const A*>(this)->doStuff(…);
答案 1 :(得分:1)
仅在const
类的实例为A
时调用const
重载。这是因为声明结尾处的const
:
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
最后的const限定符适用于this
对象,而不适用于参数。删除const
会导致歧义,因此请使用StenSoft的方式使其生效。
答案 2 :(得分:1)
我不认为这是一个正确的过载:
void doStuff(function<void (const string *)> func) const
和
void doStuff(function<void (string *)> func)
重载函数应具有相同的原型但不同的参数。在您的情况下,只有在对象为const
时才能调用const方法。这里只有两种可以在不同情况下调用的方法,但这些情况不是由overloading
机制或任何其他与参数相关的特性引起的。
另外,为什么不让人们使用你的对象和默认迭代器?在你的对象中实现begin()
和end()
方法,让人们在没有你的界面但是std lib接口的情况下做他们想做的一切:他们将能够使用ranged-for,像find
这样的算法和find_if
以及其他好事。
答案 3 :(得分:1)
元编程样板:
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...>;
检测类型表达式是否代表有效调用的特征:
// 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...>
{};
现在,在班级中替换doStuff
。第一个检测是否可以使用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);
}
此代码会检测您无法使用std::string const*
进行调用,并且您 可以使用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);
});
}
这也消除了示例中std::function
的不必要的类型擦除,并将可以转到const
方法的任何调用路由到const
方法。
另外,存储std::vector<std::string*>
几乎总是一个坏主意。
答案 4 :(得分:1)
您的推理可能是签名为void(const string *)
的lambda无法使用string *
调用,因此未转换为function<void (string *)>
。这是不正确的,因为string *
可隐式转换为const string *
,因此从期望function<void (string *)>
的函数对象构造const string *
对象是完全合法的。只有正面的情况是不允许的。这意味着编译器确定两个参数转换都是可行的(具有相同的等级)。这会使两个候选者在重载决策中不明确,但因为隐式this
的指针不是const
,所以非const
重载是首选的(隐式{{的等级) 1}}对于this
的非const
与“转化”是“完全匹配”。
如前所述,解决方案是确保隐式const
的指针是this
。这将消除候选集中的非const
重载并强制调用预期的重载。