一组概念指针

Moe*_*e42 6 c++ arrays c++-concepts gcc6 c++20

我试图弄清楚我是否可以将概念用作类的接口,而不需要虚拟表的开销.我把一个实例组合在一起,但是我必须将我的类实例存储在由它们的公共继承而不是它们的共同概念定义的数组中.我没有看到关于概念数组的帖子中讨论的任何内容,但g ++ 6.3.0似乎不允许它.错误是:

$ g++ -fconcepts -std=c++1z custom_concept.cpp 
custom_concept.cpp: In function ‘int main()’:
custom_concept.cpp:37:20: error: ‘shapes’ declared as array of ‘IShape*’
    IShape* shapes[2] = {&square, &rect};  // doesn't work 
                    ^
custom_concept.cpp:39:25: error: ‘shapes’ was not declared in this scope
    for (IShape* shape : shapes ) 
                         ^~~~~~
Run Code Online (Sandbox Code Playgroud)

如果我将IShape*数组更改为Rectangle*数组(如导致第一个错误的数组下面的注释行),程序将按预期编译并运行.

为什么不允许使用概念指针数组?这可能会在未来的c ++版本中被允许吗?

(我的例子包括虚函数和继承,尽管我的目标是消除它们.我只是为了方便Rectangle*版本才能使用它们.如果我能让IShape*版本工作,我打算删除虚函数和遗产.)

这是代码:

#include <iostream>

template <typename T>
concept bool IShape = requires (T x, T z, int y)
{
    { T() } ;
    { T(x) }  ;
    { x = z } -> T& ;
    { x.countSides() } -> int ;
    { x.sideLength(y) } -> int ;
};

struct Rectangle
{
    Rectangle() {};
    Rectangle(const Rectangle& other) {};
    Rectangle& operator=(Rectangle& other) {return *this; };
    virtual std::string getName() { return "Rectangle"; }

    int countSides() {return 4;}
    virtual int sideLength(int side) { return (side % 2 == 0) ? 10 : 5; }
};

struct Square : public Rectangle
{
    Square() {};
    Square(const Square& other) {};
    Square& operator=(Square& other) {return *this; };
    std::string getName() override { return "Square"; }
    int sideLength(int side) override { return 10; }
};

int main()
{
    Square square;
    Rectangle rect;
    IShape* shapes[2] = {&square, &rect};  // doesn't work 
//  Rectangle* shapes[2] = {&square, &rect}; // works 
    for (IShape* shape : shapes )
    {
        for (int side = 0 ; side < shape->countSides() ; ++side )
        {
            std::cout << shape->getName() << " side=" << shape->sideLength(side) << "\n";
        }
    }

    return 0;
};
Run Code Online (Sandbox Code Playgroud)

感谢@Yakk关于使用元组的想法.G ++ 6.3.0没有完全实现#include文件以包含apply(),正如C++ 17标准定义的那样,但它在std :: experimental中可用.(我认为它可能会在g ++的更高版本中添加.)这是我最终得到的结果:

#include <iostream>
#include <tuple>
#include <experimental/tuple>

template <typename T>
concept bool IShape = requires (T x, T z, int y)
{
   { T() } ;
   { x = z } -> T& ;
   { T(x) }  ;
   { x.countSides() } -> int ;
   { x.sideLength(y) } -> int ;
};

struct Rectangle
{
   Rectangle() {};
   Rectangle(const Rectangle& other) {};
   Rectangle& operator=(Rectangle& other) {return *this; };

   std::string getName() { return "Rectangle"; }
   int countSides() {return 4;}
   int sideLength(int side) { return (side % 2 == 0) ? 10 : 5; }
};

struct Square
{
   Square() {};
   Square(const Square& other) {};
   Square& operator=(Square& other) {return *this; };  

   std::string getName() { return "Square"; }
   int countSides() {return 4;}
   int sideLength(int side) { return 10; }
};

void print(IShape& shape)
{
   for (int side = 0 ; side < shape.countSides() ; ++side )
   {
      std::cout << shape.getName() << " side=" << shape.sideLength(side) << "\n";
   }
};

int main()
{
   Square square;
   Rectangle rect;
   auto shapes = std::make_tuple(square, rect);
   std::experimental::apply([](auto&... shape) { ((print(shape)), ...); }, shapes) ;

   return 0;
};
Run Code Online (Sandbox Code Playgroud)

Yak*_*ont 4

这是不可能的。

我的意思是您可以实现自己的类型擦除来替换 virtusl 函数表。在您的具体情况下,它可能比 vtable 性能更高,因为您可以根据您的具体问题定制它。

要从编译器获得帮助,这样您就不必编写样板/粘合代码,您需要反射和具体化支持以及辅助概念。

如果你这样做,它看起来会像:

ShapePtr shapes[2] = {&square, &rect};
Run Code Online (Sandbox Code Playgroud)

或者

ShapeValue shapes[2] = {square, rect};
Run Code Online (Sandbox Code Playgroud)

现在,这并不能实现您希望的性能明智的一切;类型擦除仍然会跳过函数指针。并且有每个对象或视图的存储开销。然而,您可以用更多的存储来换取更少的间接性。

这里的手动类型擦除基本上是在 C 中实现一个对象模型,然后将其包装成在 C++ 中看起来很漂亮。默认的 C++ 对象模型只是一种可能的方法,C 程序可以实现许多替代方法。

您还可以退一步,用元组替换数组。元组可以存储非统一类型,并且通过大量工作,您可以迭代它们:

auto shapes = make_IShapePtr_tuple(&square, &rect);

foreach_elem( shapes,[&](IShape* shape )
{
    for (int side = 0 ; side < shape->countSides() ; ++side )
    {
        std::cout << shape->getName() << " side=" << shape->sideLength(side) << "\n";
    }
});
Run Code Online (Sandbox Code Playgroud)

其中 lambda 获取非类型擦除类型。

这些都不需要概念:

auto shapes = std::make_tuple(&square, &rect);

foreach_elem( shapes,[&](auto* shape )
{
    for (int side = 0 ; side < shape->countSides() ; ++side )
    {
        std::cout << shape->getName() << " side=" << shape->sideLength(side) << "\n";
    }
});
Run Code Online (Sandbox Code Playgroud)

上面可以用编写。

C foreach_elem看起来像:

template<class T, class F>
void foreach_elem( T&& t, F&& f ) {
  std::apply( [&](auto&&...args){
    ( (void)f(decltype(args)(args)), ... );
  }, std::forward<T>(t) );
}
Run Code Online (Sandbox Code Playgroud)

中,lambda 中的行是:

    using discard=int[];
    (void)discard{ 0,((void)f(decltype(args)(args)),0)... };
Run Code Online (Sandbox Code Playgroud)

这有点迟钝,需要实现std::apply.

中,您必须在外部编写一个模仿 lambda 的结构。