成员方法模板返回类模板实例

时间:2019-02-10 00:14:37

标签: c++ templates pointer-to-member

我正在尝试创建一个包装类Wrapper,该包装类具有函数callMethod,该函数调用包装对象的成员方法,但返回包装在Wrapper实例中的返回值。

我的第一种方法无效,我也不明白为什么。我设法找到了一种可行的解决方法,但我不明白为什么。我想请教一下这里的c ++模板黑魔法。

#include <string>
#include <iostream>
#include <functional>

template <typename WrappedType>
struct Wrapper
{
    Wrapper(WrappedType value):
        m_value(value)
    {
    }

    template<typename ReturnType>
    Wrapper<ReturnType> callMethod(ReturnType (WrappedType::*method)())
    {
        return std::invoke(method, m_value);
    }

    WrappedType m_value;
};

int main()
{
    Wrapper helloString(std::string("Hello World!"));
    auto frontChar = helloString.callMethod(&std::string::front);
    std::cout << frontChar.m_value;

    return 0;
}

使用gcc 8.2.0(带有选项-std=c++17)编译上述代码会产生以下编译器错误:

prog.cc: In instantiation of 'struct Wrapper<char&>':
prog.cc:25:64:   required from here
prog.cc:14:25: error: forming pointer to reference type 'char&'
     Wrapper<ReturnType> callMethod(ReturnType (WrappedType::*method)())
                         ^~~~~~~~~~

该错误表明问题出在实例化Wrapper<char&>上(实例化发生在调用helloString.callMethod(&std::string::front)时,返回类型为Wrapper<char&>)。我的猜测是发生错误是因为Wrapper<char&>::callMethod无效(char&不是类类型,因此它没有成员方法)。我虽然从未使用过Wrapper<char&>::callMethod,但为什么会导致错误呢?模板类的成员方法仅在使用时才编译,对吧?

按预期修改callMethod的返回类型可以解决该错误,因为Wrapper<char&>不再实例化。但是,这不能解决原始问题。

// works but the return value is not wrapped
template<typename ReturnType>
ReturnType callMethod(ReturnType (WrappedType::*method)())
{
    return std::invoke(method, m_value);
}

接下来,我试图弄清楚为什么Wrapper<char&>::callMethod如果不调用会引起问题。也许编译器无法弄清楚WrappedType是那里的从属名称,并且在两阶段查找的第一阶段会对其进行检查?我什至不知道这是否有意义,但我尝试使用它。

我查找了相关名称,并尝试使用typenametemplate无济于事(我不知道该在哪里放置它们)。然后,我尝试向callMethod添加第二个模板参数,该参数默认为WrappedType并最终编译,但是我不知道为什么。也许是因为编译器现在将其视为从属名称,并且仅在查找的第二阶段才检查指向成员函数的指针?

// compiles and works fine
template<typename ReturnType, typename ClassType = WrappedType>
Wrapper<ReturnType> callMethod(ReturnType (ClassType::*method)())
{
    return std::invoke(method, m_value);
}

所以我设法找到一种解决方法,但是我有几个问题:

  • 为什么我的原始方法会导致编译器错误?
  • 为什么我的第二个解决方法可以解决此编译器错误?
  • 是否可以在不添加第二个模板参数的情况下修改原始代码以解决编译器错误?如果是,怎么办?

0 个答案:

没有答案