【问题标题】:Templated member function and inheritence模板化成员函数和继承
【发布时间】:2023-01-15 14:36:26
【问题描述】:

我在一个类中声明了一个模板成员函数,它根据类型调用正确的成员函数,并希望通过添加一个成员函数在子类中为其添加一些功能,如下面的 main.cpp 示例所示:

#include <iostream>

class A
{
public:
    template <typename T>
    void handleSocketData(const T& t)
    {
        handleData(t);
    }

    void handleData(int data)
    {
        std::cout << data << std::endl;
    }

};

class B: public A
{
public :
    void handleData(std::string data) const
    {
        std::cout << data << std::endl;
    }
};

int main(int argc, char *argv[])
{
    A a;
    B b;
    a.handleSocketData<int>(30);
    b.handleSocketData<std::string>("Hi");
    return 0;
}

我的问题是 b.handleSocketData&lt;QString&gt;("Hi"); 实际上确实在 A 类中生成了一个新的模板实例,如命令 /usr/bin/clang++ -DQT_CORE_LIB -isystem /usr/include/qt6/QtCore -isystem /usr/include/qt6 -isystem /usr/lib64/qt6/mkspecs/linux-g++ -g -std=gnu++17 -Xclang -ast-print -fsyntax-only main.cpp 的输出所示:

class A {
public:
    template <typename T> void handleSocketData(const T &t) {
        this->handleData(t);
    }
    template<> void handleSocketData<int>(const int &t) {
        this->handleData(t);
    }
    template<> void handleSocketData<std::basic_string<char>>(const std::basic_string<char> &t) {
        <recovery-expr>(this->handleData, t);
    }
    void handleData(int data) {
        std::cout << data << std::endl;
    }
};
class B : public A {
public:
    void handleData(std::string data) const {
        std::cout << data << std::endl;
    }
};
int main(int argc, char *argv[]) {
    A a;
    B b;
    a.handleSocketData<int>(30);
    b.handleSocketData<std::string>("Hi");
    return 0;
}

所以现在我有一个编译错误,说没有找到函数 handleData(const std::string& data),这是正常的。

我们发现的解决方法是定义一个双参数模板,将子类作为参数(一种访问者模式):

#include <iostream>

class A
{
public:
    template <typename T, typename U>
    void handleSocketData(U& u, const T& t)
    {
        u.handleData(t);
    }

    void handleData(int data)
    {
        std::cout << data << std::endl;
    }

};

class B: public A
{
public :
    void handleData(std::string data)
    {
        std::cout << data << std::endl;
    }
};

int main(int argc, char *argv[])
{
    A a;
    B b;
    a.handleSocketData<int>(a, 30);
    b.handleSocketData<std::string>(b, "Hi");
    return 0;
}

你怎么认为 ?有更清洁的方法吗?

【问题讨论】:

  • 从来不知道班级有性别。如果将其更改为子类怎么办?
  • 您的解决方案看起来像 deducing this (C++23)。
  • 我的假设是否正确,模板化的handleSocketData 出于某种原因需要存在(满足外部 API 或其他)?因为对于您提供的简单示例,由于重载解析不需要它:godbolt.org/z/x64osr3M4

标签: c++ c++17


【解决方案1】:

这看起来像是 CRTP 的经典用例。您可以使 A 成为派生类 Derived 的模板,然后通过 static_cast 将函数调用分派给派生类。为此,任何派生类 Derived 都必须派生自 A&lt;Derived&gt;。

由于您似乎想将 A 用作非抽象类,因此您必须添加一个默认派生类,将其标记为“最终”。在以下代码中,空结构 FinalTag 用于此目的。

#include <iostream>

struct FinalTag;

template <typename Derived=FinalTag>
class A
{
public:
    template <typename T>
    void handleSocketData(const T& t)
    {
        cast().handleData(t);
    }

    void handleData(int data)
    {
        std::cout << data << std::endl;
    }

private:
    constexpr auto& cast() {
        return static_cast<Derived&>(*this);
    }

};

struct FinalTag : A<FinalTag> {};

class B: public A<B>
{
public :
    using Base = A<B>;
    using Base::handleData;

    void handleData(std::string data)
    {
        std::cout << data << std::endl;
    }
};

int main(int argc, char *argv[])
{
    A a;
    B b;
    a.handleSocketData(30);
    b.handleSocketData("Hi");

    // this only works if you bring in Base::handleData in the 
    // derived class
    b.handleSocketData(30);
    return 0;
}

直播码:https://godbolt.org/z/ns9aPjG76

这是一个原型。例如,您可能希望向 cast 方法添加一个 const 版本。

编辑:

正如 Jarod42 在 cmets 中指出的那样,C++23 确实通过“推导这个”简化了 CRTP:https://godbolt.org/z/cGzMrnEhc。不过,目前编译器并未广泛支持这一点。

【讨论】:

  • 可能是 constexpr auto&amp; cast() 或 constexpr decltype(auto) cast()。
  • @aschepler 谢谢,你是对的。修复。
【解决方案2】:

在某些情况下,与 Joerg Brech 建议的版本略有不同的 CRTP 版本可能更合适。

#include <iostream>

class A
{
public:
    template <class Class, typename T>
    void handleSocketData(const T& t)
    {
        static_cast<Class*>(this)->handleData(t);
    }

    void handleData(int data)
    {
        std::cout << data << std::endl;
    }

};

class B: public A
{
public :
    void handleData(std::string data) const
    {
        std::cout << data << std::endl;
    }
};

int main(int argc, char *argv[])
{
    A a;
    B b;
    a.handleSocketData<A, int>(30);
    b.handleSocketData<B, std::string>("Hi");
    return 0;
}

它与您的解决方案非常相似,因为我们指示handleSocketData 它应该使用哪个类来调用handleData。唯一的区别是决定不是动态的而是在编译时做出的。

【讨论】:

  • 最好颠倒模板参数顺序,因此可以推断出 int(仍然需要 std::string,因为 "Hi" 不是 std::string(而是 const char (&amp;) [3])
  • @Jarod42,谢谢你的建议
  • 也喜欢这个解决方案,因为它更简单。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-05-07
  • 1970-01-01
  • 2012-10-26
  • 2014-11-11
  • 2017-01-05
  • 2016-12-03
  • 2020-08-12
相关资源
最近更新 更多