【问题标题】:Disable method override in template derived class在模板派生类中禁用方法覆盖
【发布时间】:2014-06-16 17:12:38
【问题描述】:

是否可以在派生类中禁用Foo() 覆盖(通过std::enable_if 或一些增强魔法),以防T 不是特定类型,而不必为@ 编写模板特化987654323@?

加分项:如果 T 没有定义某个方法,是否可以禁用覆盖?

这是我的 SSCCE:

#include <iostream>
#include <string>

class Base
{
public:
    virtual std::string Foo()
    {
        return "Base";
    }
};

template <typename T>
class Derived : public Base
{
public:
    virtual std::string Foo() override
    {
        return "Derived";
    }
};

int main()
{
    Derived<int> testInt;
    std::cout << testInt.Foo() << std::endl;

    Derived<float> testFloat;
    std::cout << testFloat.Foo() << std::endl;//I would like this to print 'Base'
}

更新:

感谢您提供出色的解决方案,但我无法使它们适应我的真实代码。以下示例应该更好地了解我想要实现的目标:

#include <iostream>
#include <string>

class Object
{
public:
    void Test()
    {
        std::cout << "Test" << std::endl;
    }
};

class EmptyObject
{
};

class Base
{
public:
    virtual std::string Foo()
    {
        return "Base";
    }
};

template <typename T>
class Derived : public Base
{
public:
    virtual std::string Foo() override
    {
        m_object.Test();
        return "Derived";
    }

private:
    T m_object;
};

int main()
{
    Derived<Object> testObject;
    std::cout << testObject.Foo() << std::endl;

    Derived<EmptyObject> testEmpty;
    std::cout << testEmpty.Foo() << std::endl;
}

【问题讨论】:

  • C++11你可以标记函数final并且它不能被覆盖。但是,我不知道您是否可以使用虚函数来做到这一点。
  • 那无济于事。我确实需要覆盖从 Base 派生的一些类中的方法(这只是一个简单的例子)
  • @user2899162 当然可以用虚函数来完成。 而且只有他们。
  • 啊@Constructor,不确定我是否混淆了我的语法。很高兴知道,谢谢。

标签: c++ templates inheritance boost enable-if


【解决方案1】:

我将通过创建两个函数来做到这一点,Derived::Foo 可以根据 T = float 是否有条件地委托给它们。一个将包含真正的Derived::Foo 实现,而另一个将调用Base::Foo

template <typename T>
class Derived : public Base
{
public:
    virtual std::string Foo() override
    {
        return do_Foo(std::is_same<T, float>{});
    }

private:
    std::string do_Foo(std::false_type)
    {
        return "Derived";
    }
    std::string do_Foo(std::true_type)
    {
        return Base::Foo();
    }
};

Live demo


看来您真正想要做的是仅当T 定义了某个成员函数时才调用Derived&lt;T&gt;::Foo() 实现,否则应调用Base::Foo()。这可以使用表达式 SFINAE 来完成。

template <typename T>
class Derived : public Base
{
public:
    std::string Foo() override
    {
        return do_Foo(true);
    }
private:
    template<typename U = T>
    auto do_Foo(bool)
        -> decltype(std::declval<U>().test(), void(), std::string())
    {
        return "Derived";
    }
    std::string do_Foo(int)
    {
        return Base::Foo();
    }
};

Live demo

在上面的代码中,如果T 类型没有定义名为test() 的成员函数,则do_Foo(bool) 成员函数模板将不可行。另一方面,如果T::test() 确实存在,则将选择do_Foo(bool),因为与do_Foo(int) 相比,Foo 传递给do_Foo 的布尔值使其更匹配。

有关尾随返回类型中 decltype 表达式的详细说明,请参见 here

【讨论】:

  • 这看起来是一个不错的解决方案,但遗憾的是,它在我的真实代码中不起作用,因为 do_Foo 需要在 T 类型的对象上调用方法,所以我需要尝试别的东西……
  • @MihaiTodor 您可以将对象转发给do_Foo()。如果您显示 Foo() 函数的签名是什么样的,我可以更新答案。
  • @MihaiTodor 我没有看到问题。在std::string do_Foo(std::false_type) 中调用m_object.Test();,不要在std::string do_Foo(std::true_type) 中调用它。这解决了问题,不是吗?
  • @MihaiTodor 我已经更新了答案。当我第一次回答时,我错过了您真正需要调用Derived:Foo() 的部分,前提是T 定义了特定的成员函数。编辑:更新的东西可能不适用于 VS,因为它还不支持表达式 SFINAE。
  • @MihaiTodor 检查我最后链接到的答案。解释太长了,我不想再复制粘贴一遍。
【解决方案2】:

您可以直接对方法进行模板特化,而不是模板特化类:(https://ideone.com/gYwt5r)

template<> std::string Derived<float>::Foo() { return Base::Foo(); }

而且我只看到一个类的模板专门化,通过将final 添加到虚拟方法来禁用未来覆盖取决于T

【讨论】:

  • 但是,这也需要我将方法设为模板,对吧?
  • 不,方法不是模板。完整代码见链接。
  • 啊,对。对不起,愚蠢的评论,感谢您的帮助。它确实有效,但对于我的真实代码,我发现@Praetorian 的建议更灵活。
【解决方案3】:

如果您需要在编译时限制某种类型,可以使用std::enable_ifstd::is_same

typename std::enable_if<std::is_same<T, float>::value, std::string>::type 
virtual Foo() override
{
    return "Derived";
}

或者,如果模板类型不是您要查找的类型,您可以轻松地将调用重定向到 Base 方法,仍然使用 std::is_same

virtual std::string Foo() override
{
    return std::is_same<T, float>::value ? Base::Foo() : "Derived";
}

至于 Bonus,你可以从这个SO answer 获得 trait,这里用decltype 改编,用于方法bar()

template <typename T>
class has_bar
{
    typedef char one;
    typedef long two;

    template <typename C> static one test(decltype(&C::bar) ) ;
    template <typename C> static two test(...);

public:
    enum { value = sizeof(test<T>(0)) == sizeof(char) };
};

限制是您不能对参数或返回类型施加约束。

virtual std::string Foo() override
{
    return has_bar<T>::value ? "Derived" : Base::Foo() ;
}

注意:

您也可以在我的第一个示例中将has_barenable_if 一起使用,以在编译时禁用它。

【讨论】:

  • 这看起来很有希望,但我认为我在 enable_if 上做错了什么:ideone.com/MTWBu8
  • 不,你不是:它没有 编译 因为 enable_if 删除了方法(见我的帖子,它在我的第一行),这就是 enable_if 的意义(使用其他方法,如果您仍然希望它在与不需要的类型一起使用时编译)
  • 哦,对了。我现在明白了。可悲的是,我认为我无法让重定向技巧在我的情况下起作用。这个例子应该更接近真实情况:ideone.com/C6g6tx
  • 这和“红利”有关吧?你可以使用 trait has_bar 并且改变是 to has_test 完全相同的方式。那有意义吗 ?你想要编辑吗?
  • 如果您能抽出一些时间,我很乐意看到一个可行的示例。现在,我想我将能够通过@Praetorian 的解决方案实现我的目标。非常感谢您的努力!
【解决方案4】:

您可以在层次结构中添加一个中间类:

class Base
{
public:
    virtual std::string Foo()
    {
        return "Base";
    }
};

template <typename T>
class Intermediate : public Base
{
    // common operations with m_object

protected: // not private!
    T m_object;
};

template <typename T, typename = bool>
class Derived : public Intermediate<T> {};

template <typename T>
class Derived<T, decltype(std::declval<T>().Test(), void(), true)>
    : public Intermediate<T>
{
public:
    virtual std::string Foo() override
    {
        this->m_object.Test(); // this-> is necessary here!
        return "Derived";
    }
};

完整的示例使用clang 3.4g++ 4.8.2 编译成功。

【讨论】:

  • 是的,确实,但我试图避免这种情况,因为我的修改位于遗留代码的深处,我可能会破坏某些东西......另外,VS2012 似乎没有就像表达式 SFINAE 技巧一样,这是一种耻辱,所以我最终使用 std::is_same&lt;T, MyObject&gt; 来检测要为其调用 Test() 的类型,这是我不喜欢在通用代码中做的事情,但我不喜欢'现在不想弄乱sizeof 方法检测技巧...
猜你喜欢
  • 1970-01-01
  • 2016-03-17
  • 1970-01-01
  • 2018-06-06
  • 2019-03-28
  • 1970-01-01
  • 2012-09-15
  • 1970-01-01
  • 2020-11-05
相关资源
最近更新 更多