I am trying to figure out if I can use concepts as a kind of interface for classes without requiring the overhead of a virtual table. I put together an example which sort-of works, but I have to store my class instances in an array defined by their common inheritance rather than their common concept. I don't see anything discussed in posts about arrays of concepts, but g++ 6.3.0 does not appear to allow it. The error is:
$ 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 )
^~~~~~
If I change the IShape*
array to a Rectangle*
array (as in the commented line line below the one that caused the first error), the program compiles and runs as expected.
Why is it that the array of concept pointers is not allowed? Will this likely be allowed in a future version of c++?
(My example includes virtual functions and inheritance, even though my goal was to eliminate them. I included them only as a convenience to get the Rectangle*
version to work. If I can get the IShape*
version to work, I plan to remove the virtual functions and the inheritance.)
Here is the code:
#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;
};
Thanks to @Yakk for the idea about using tuple. G++ 6.3.0 hadn't fully implemented the #include file to include apply() as the C++17 standard defines, but it was available in std::experimental. (I think it may be added to in a later version of g++.) Here's what I ended up with:
#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;
};