J'ai besoin de créer une classe de modèle en C ++. Je dois m'assurer que le type du paramètre de modèle sera une classe avec 1 champ int et 1 champ de chaîne (il peut y avoir plus de champs, mais ceux-ci sont obligatoires).

Par exemple, en C #, je pourrais définir une interface avec des méthodes ou des propriétés, comme ceci:

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

Puis je pourrais l'utiliser dans ma classe de modèle:

class MyClass<T> where T: MyInterface {}

Existe-t-il un moyen de faire quelque chose comme ça en C ++?

4
user13353974 5 mai 2020 à 22:00

3 réponses

Meilleure réponse

La manière la plus courante de faire cela dans les versions actuelles de C ++ est une technique connue sous le nom de "canard-typing".

Il s'agit simplement d'utiliser T comme s'il implémentait l'interface et de laisser le compilateur échouer si vous utilisez la classe avec un type incompatible.

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();
} 

Remarquez qu'il existe des moyens d'appliquer cela un peu plus formellement, mais la quantité de code impliquée ne vaut souvent pas l'avantage.

3
Frank 5 mai 2020 à 19:11

C ++ 20 a des concepts. Certains compilateurs les prennent déjà en charge. Par exemple ce qui suit avec 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{});
}

Résulte en:

<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

Démo en direct

Avant C ++ 20, vous pouvez utiliser SFINAE. Cependant, il est souvent plus simple et plus approprié de ne pas restreindre le paramètre tempalte plus que nécessaire. Si le modèle appelle T::GetSomeInteger() mais que le type T n'a pas une telle méthode, le modèle échouera déjà à se compiler sans prendre d'autres mesures. SFINAE est principalement de fournir des messages d'erreur plus agréables.

2
idclev 463035818 5 mai 2020 à 20:34

C ++ 20 vous offre la solution la plus proche de C #:

#include <concepts>

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

Puis:

template <MyInterface T>
struct MyClass
{
    // ...
};
7
Acorn 5 mai 2020 à 19:45