首页 > 解决方案 > 函数参数能否保证一个类符合接口?

问题描述

是否可以让一个函数接受一个“基类”并指定它需要符合某个接口?例如:

class A{};
class ISomeInterface{..};
class B:public A, public ISomeInterface{};
class C:public A, public ISomeInterface{};

其他类(不是从 派生的A)也可以使用ISomeInterface,并不是每个派生的类A都需要实现ISomeInterface。现在我想让一个函数接受 A 的任何子类,只要它也在实现ISomeInterface,所以像这样;

void doSomething(A<ISomeInterface> *a){}; // or (A&&ISomeInterface *a) or (A::ISomeInterface a*) or whatever 

我不想直接指定 B 或 C 作为参数,但我希望它们作为参数。我唯一知道的是,我希望基类成为A并且传递的任何子类A都必须实现ISomeInterface。在现代语言中,这真的很容易,但我似乎不知道如何在 C++ 中做到这一点。这是模板的用途吗?

标签: c++

解决方案


使用 C++20 概念,这是微不足道的:

#include <concepts>

struct iface {
    virtual void do_a() = 0;
};

struct B : public iface {
    virtual void do_a() override {}
};

template <typename T>
struct A {
    T value;
};

template <std::derived_from<iface> T>
void call_iface(A<T> &some_a) {
    some_a.value.do_a();
}

int main() {
    A<B> a {};

    call_iface(a);

    // This won't compile
    
    // A<int> v {};
    // call_iface(v); 
    
    return 0;
}

使用概念,您可以对函数和模板参数设置约束。仅当是 的基类std::derived_from<Derived, Base>时才解析为真。BaseDerived

这基本上形式化了在旧 C++ 版本中使用普通 SFINAE 可以完成的工作。例如,可以使用 C++11 实现类似的结果std::enable_if

#include <type_traits>

struct iface {
    virtual void do_a() = 0;
};

struct B : public iface {
    virtual void do_a() override {}
};

template <typename T>
struct A {
    T value;
};

template <typename T, typename = typename std::enable_if<std::is_base_of<iface, T>::value>::type>
void call_iface(A<T> &some_a) {
    some_a.value.do_a();
}

int main() {
    A<B> a {};

    call_iface(a);

    // This won't compile
    
    // A<int> v {};
    // call_iface(v); 
    
    return 0;
}

SFINAE 是一个巧妙的 hack,它滥用了在 C++ 中失败的模板替换会导致它被忽略而不是导致错误这一事实。此模式已被 C++11 中的标准识别并由 instd::enable_if形式化<type_traits>

编辑:请注意,在此示例中,我没有准确描述您的用例(即A 的任何子类,只要它也实现 ISomeInterface),但通过组合需求很容易适应。

例如,您的用例可以这样表示:

#include <concepts>

struct A {};

struct iface {
    virtual void do_a() = 0;
};

struct B : public A, public iface {
    virtual void do_a() override {}
};

struct C : public A {};

template <typename T>
    requires std::derived_from<T, A> && std::derived_from<T, iface>
void call_iface(T &both) {}

int main() {
    B b {};

    call_iface(b);

    // This won't compile
    
    // C v {};
    // call_iface(v); 
    
    return 0;
}

推荐阅读