【问题标题】:Ensure class methods with C++20 concepts确保具有 C++20 概念的类方法
【发布时间】:2020-11-10 01:50:43
【问题描述】:

我喜欢 Java 接口的特性,并期待新的 C++20 标准,引入概念。

在当前的项目中,我将对同一件事进行多个实现。其余代码应该不受此影响,并以一般“一刀切”的方式处理它们。此外,为了帮助其他人编写自己的这个可交换部分的实现,我希望有一个文档的中心位置,描述所有需要的部分。

我试着让它工作了一段时间,但我一直在为 C++20 的概念而苦苦挣扎。由于没有什么真正起作用,我用一个小例子描述了我想要的:

/* 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>;

上述概念MyInterface 应该确保,通过my_function&lt;MyObject&gt;() 调用下面的函数应该适用于不同的实现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();
}

我对此有 3 个问题:

  1. 是否有可能通过概念来实现?
  2. 这是否可能看起来有点干净?由于它应该通过可访问的文档来增加可读性,因此如果概念的代码几乎不可读,它将没有用。
  3. 一般来说,概念是正确的方法,还是有其他/更好的方法来实现?

谢谢,莫罗

【问题讨论】:

  • 请每个 stackoverflow.com 问题一个问题。
  • 1.是的,2. 基于意见,但对我来说似乎很好(概念命名除外) 3. 基于意见,但似乎是个好方法,还有其他方法(根本不检查,SFINAE)
  • "我喜欢 Java 中的接口特性,并期待新的 C++20 标准,引入概念。" 这不是概念的用途。 Java“接口”只是 C++ 中的纯虚拟类。

标签: c++ c++20 c++-concepts


【解决方案1】:

你问了很多事情,所以我一一回答。首先是您的“HasElement”概念。

在这里你可以看到它是如何工作的:

#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{});
}

通过概念,您基本上可以为一组需求命名。如果一个概念很长,你不需要一直重复。

【讨论】:

    【解决方案2】:

    你有一个合理的想法,但这不是 requires-expressions 的语法。你想要类似的东西

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

    请注意,无需单独测试 Class::Element:如果该类型不存在,则原子约束只需根据需要计算为 false

    这并不像您的措辞所暗示的那样严格;例如,类可以从int 构造(可能通过隐式转换、默认参数、构造函数模板等)就足够了,并且它完全忽略了foobar 的返回类型。然而,作为约束作者的普遍建议,你为什么关心foobar 是否返回一些东西?如果您希望它是void,那么无论如何您都不会对返回值做太多事情。通常最好要求 将使用的接口(例如,您将在此处传递int 并在此处忽略值)而不是尝试描述实现有问题的类型。因此,您也可以考虑放宽 std::same_as,也许使用 std::convertible_to

    【讨论】:

      猜你喜欢
      • 2021-07-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-03
      • 2019-11-20
      • 1970-01-01
      • 1970-01-01
      • 2021-11-27
      相关资源
      最近更新 更多