简体   繁体   English

使用 C++20 概念确保类方法

[英]Ensure class methods with C++20 concepts

I love the feature of Interfaces in Java, and was looking forward to the new C++20 standart, introducing concepts.我喜欢 Java 中接口的特性,并期待新的 C++20 标准,引入概念。

On a current project i will have multiple implementations for the same thing.在当前的项目中,我将对同一件事有多个实现。 The rest of the code should be unaffected by that and handel them all in a general "one fits all" way.其余的代码应该不受此影响,并以一般的“一刀切”方式处理它们。 Further, to help other people coding there own implementation of this exchangeable part, i would like to have a central place for the documentation, describing all needed parts.此外,为了帮助其他人编码自己的这个可交换部分的实现,我希望有一个文档的中心位置,描述所有需要的部分。

I tried to get this working for some time now, but i keep one struggeling with the C++20 concepts.我试图让它工作一段时间,但我一直在努力使用 C++20 概念。 Since nothing really worked i discribe what i would like to have with a small example:由于没有任何实际效果,我用一个小例子描述了我想要的东西:

/* Should have a element type, like float, int, double, std::size_t,... */
template <typename Class>
concept HasElementType = requires {
    typename Class::Element;
};

/* Central place for the documentation: in the concept.
 * Since all relevant parts should be listed here, they can be documentated.
 */
template < typename Class, typename T>
concept HasFunctions = requires {
    Class::Class(int);    /* has constructor with int */
    T Class::field;       /* has field with name "field" of type T */
    int Class::foo(T);    /* has method foo, taking T, returning int */
    T Class::bar(int);    /* has method bar, taking int, returning T */
    void Class::foobar(); /* has method foobar, taking void, returnung void */
};

/* put both togetter */
template <typename Cls>
concept MyInterface = HasElementType<Cls> && HasFunctions<Cls,typename Cls::Element>;

The above concept MyInterface should than ensure, that calling the function below via my_function<MyObject>() should work properly for different implementations MyObject{Implementaion1, Implementaion2,...} .上面的概念MyInterface应该确保,通过my_function<MyObject>()调用下面的函数应该适用于不同的实现MyObject{Implementaion1, Implementaion2,...}

/* Some example function */
template<MyInterface MyObejct>
void my_function(){
    using T = MyObejct::Element;
    T t = 5;
    MyObejct myObject(1);
    T field = myObject.field;
    int foo = myObject.foo(t);
    T bar   = myObject.bar(1);
    myObject.foobar();
}

I have 3 questions regarding this:我对此有3个问题:

  1. Is it possible with concepts, to accomplish that?是否有可能通过概念来实现?
  2. Is this in a somewhat clean look possible?这可能看起来有点干净吗? Since it should increase the readability via accessible documentation, it would not be usefull if the code for the concept is barely readable.由于它应该通过可访问的文档提高可读性,如果概念的代码几乎不可读,它就没有用。
  3. Are concepts in generall the right approche, or are there other/better ways to accomplish that?概念通常是正确的方法,还是有其他/更好的方法来实现这一目标?

Thanks, moro谢谢,莫罗

You haved asked multiple things, so I answer one by one.你问了很多,我一一回答。 First your "HasElement" concept.首先是您的“HasElement”概念。

Here you can see how it works:在这里你可以看到它是如何工作的:

#include <iostream>
#include <type_traits>

class AWithStaticElement{
public:
    static int Element;
};
int AWithStaticElement::Element = 12;

class AWithInstanceElement{
public:
    int Element;
};

class AWithElementType{
public:
    using Element = int;
};

class AWithoutElement{
};

template<typename T>
    requires std::is_member_pointer_v<decltype(&T::Element)>
void Foo(T t)
{
    std::cout << "Has instance Element " << t.Element << "\n";
}

template<typename T>
requires std::is_pointer_v<decltype(&T::Element)>
void Foo(T t) 
    
{
    std::cout << "Has static Element " << t.Element << "\n";
}

template<typename T>
requires requires (T t) { typename T::Element; }
void Foo(T t)
{
    std::cout << "Has Element type\n";
}

template<typename... T>
void Foo(T&&... t)
{
    std::cout << "Has no Element!\n";
}

int main()
{
    Foo(AWithStaticElement{});
    Foo(AWithInstanceElement{});
    Foo(AWithElementType{});
    Foo(AWithoutElement{});
}

With concepts you can basically give a set of requirements a name.通过概念,您基本上可以为一组需求命名。 If a concepts is long you don't need to repeat it all the time.如果一个概念很长,你就不需要一直重复它。

You have a plausible idea, but that's just not the syntax for requires-expression s.您有一个合理的想法,但这不是requires-expression的语法。 You want something like你想要类似的东西

template < typename Class >
concept HasFunctions = requires(Class c, Class::Element e) {
    Class(1);  // don't use a null pointer constant here!
    { c.field } -> std::same_as<decltype(e)>;
    { c.foo(e) } -> std::same_as<int>;
    { c.bar(1) } -> std::same_as<decltype(e)>;
    c.foobar();
};

Note that there's no need to test Class::Element separately: if that type doesn't exist, then the atomic constraint simply evaluates to false as desired.请注意,无需单独测试Class::Element :如果该类型不存在,则原子约束根据需要简单地评估为false

This isn't quite as strict as your phrasing suggests;这并不像您的措辞所暗示的那样严格 it's sufficient that the class be constructible from an int (possibly via implicit conversions, default arguments, constructor templates, etc. ), for example, and it ignores the return type of foobar entirely.例如,类可以从int构造(可能通过隐式转换、默认参数、构造函数模板)就足够了,并且它完全忽略了foobar的返回类型。 However, as is rapidly becoming common advice for constraint authors, why do you care if foobar returns something?然而,正如约束作者的普遍建议一样,你为什么要关心foobar返回一些东西? If you expect it to be void , you're not going to do much with the return value anyway.如果您希望它是void ,那么无论如何您都不会对返回值做太多事情。 It's generally superior to require the interface that you will use ( eg , that you will pass an int here and ignore a value there) rather than trying to describe the implementation of the type in question.这是一般优于需要的界面,将使用(例如,你将通过一个int这里忽略的值存在),而不是试图描述问题的类型的实现。 Accordingly, you might consider relaxing the std::same_as as well, perhaps with std::convertible_to .因此,您也可以考虑放宽std::same_as ,也许使用std::convertible_to

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM