C++中是否有类似于C#中关键字"where"的东西?

4

我需要在C++中创建一个模板类。我需要确保模板参数的类型是一个有1个int字段和1个string字段的类(可以有更多的字段,但这两个是必需的)。

例如,在C#中,我可以定义一个带有方法或属性的接口,如下所示:

interface MyInterface {
    int GetSomeInteger();
    string GetSomeString();
}

然后我可以在我的模板类中使用它:
class MyClass<T> where T: MyInterface {}

有没有办法在C++中做类似这样的事情?


你说“field”,但你的示例包含了方法。如果你的“fields”都是方法,那么这可以很容易地通过使用std::enable_if<>std::is_base_of<>来实现,但成员变量则是一个更加复杂的问题。 - user4442671
2
我相信新引入的“概念”(C++20)是为此而设计的。 - Adam
2
在C++中,直接检查所需内容比检查继承更为常见,但是两种方式都是可行的。 - chris
3个回答

8

C++20为您提供了最接近C#的解决方案:

#include <concepts>

template <class T>
concept MyInterface = requires(T x)
{
    { x.GetSomeInteger() } -> std::same_as<int>;
};

然后:

template <MyInterface T>
struct MyClass
{
    // ...
};

为什么需要 same_as?我刚写了我的第一个概念并发布了答案,没有意识到它必须是/应该是 std::same_as<int> - 463035818_is_not_a_number
1
根据我对[expr.prim.req.compound]的阅读,返回类型要求使用一个类型约束,该约束必须是根据[temp.param]中的概念定义的。 - Acorn
1
这个博客帮助我更好地理解了一些内容。GCC的支持似乎还处于实验阶段。它不支持<concepts>,并且只接受带有-> int的行。 - 463035818_is_not_a_number
2
最初的提案使用了语法 { x.GetSomeInteger() } -> int;,但后来被认为不够清晰,容易引起混淆:这是否确切地意味着 int?它接受 cv int 吗?还是可转换为 int?而且没有明确的主要用例(有时想要其中一个,有时想要另一个)。因此现在需要一个明确的语法。 - bolov
1
为了测试概念,我建议使用大多数编译器和标准库的最新预览/HEAD版本。希望在几个月内情况会好得多! - Acorn
显示剩余2条评论

3
在当前版本的C++中,最常用的方法是一种称为“鸭子类型”的技术。 它简单地涉及使用T作为实现接口的类,并在您使用不兼容类型的类时让编译器失败。
template<typename T>
class MyClass<T> {
  int foo() {
    T val;
    return val.GetSomeInteger();
  }
};

class Valid {
public:
  int GetSomeInteger() {return 0;}
};

class Invalid {
};

int main() {
  // works fine
  MyClass<Valid> a;
  a.foo();

  // fails to compile
  MyClass<Invalid> b;
  b.foo();
} 

请注意,确实有一些更正式的强制执行方法,但涉及的代码量通常不值得收益。

1
std::enable_if 让 SFINAE 变得更容易(虽然仍然不简单),但是编译器错误得到了很大的改善,通常不必限制超过必要的程度。 - 463035818_is_not_a_number
1
std::enable_if 允许许多巧妙的技巧,但我通常更喜欢 static_assert。这个会有更容易理解的编译器错误提示。 - Mooing Duck

2

C++20引入了概念(Concepts)特性,一些编译器已经支持。例如,可以使用以下命令在gcc (trunk) -std=c++2a -fconcepts下进行编译:

#include <string>
#include <iostream>
#include <concepts>

template<typename T>
concept HasGetIntAndString = requires(T& a) {
    { a.GetSomeInteger() } -> std::same_as<int>;
    { a.GetSomeString() } -> std::same_as<std::string>;
};

template <HasGetIntAndString T>
void bar(const T& t){
    std::cout << t.GetSomeInteger() << " " << t.GetSomeString();
}

struct foo {
    int GetSomeInteger() const { return 42; }
    std::string GetSomeString() const { return "some"; }
};

struct foo_not {
    std::string GetSomeInteger() { return "some"; }
    int GetSomeString() { return 42; }
};

int main(){
    bar( foo{});
    bar( foo_not{});
}

结果是:

<source>: In function 'int main()':    
<source>:28:19: error: use of function 'void bar(const T&) [with T = foo_not]' with unsatisfied constraints   
   28 |     bar( foo_not{});    
      |                   ^    
<source>:12:6: note: declared here    
   12 | void bar(const T& t){    
      |      ^~~    
<source>:12:6: note: constraints not satisfied
<source>: In instantiation of 'void bar(const T&) [with T = foo_not]':    
<source>:28:19:   required from here    
<source>:6:9:   required for the satisfaction of 'HasGetIntAndString<T>' [with T = foo_not]    
<source>:6:30:   in requirements with 'T& a' [with T = foo_not]    
<source>:7:23: note: 'a.GetSomeInteger()' does not satisfy return-type-requirement    
    7 |     { a.GetSomeInteger() } -> std::same_as<int>;    
      |       ~~~~~~~~~~~~~~~~^~    
<source>:8:22: note: 'a.GetSomeString()' does not satisfy return-type-requirement    
    8 |     { a.GetSomeString() } -> std::same_as<std::string>;    
      |       ~~~~~~~~~~~~~~~^~    
cc1plus: note: set '-fconcepts-diagnostics-depth=' to at least 2 for more detail

实时演示

在 C++20 之前,您可以使用 SFINAE。然而,通常情况下,不要限制模板参数的更多属性会更简单和更合适。如果模板确实调用了 T::GetSomeInteger() 但类型 T 没有这样的方法,那么模板将无法编译而不需要采取任何进一步措施。SFINAE 主要是为了提供更好的错误消息。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接