使用std :: function参数重载函数:为什么const方法永远不会被调用?

时间:2015-06-29 13:13:01

标签: c++ c++11 const overloading

#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方法。

5 个答案:

答案 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重载并强制调用预期的重载。