Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Why isn't this an incomplete class definition?

27 views
Skip to first unread message

Paul

unread,
Oct 20, 2018, 6:38:40 AM10/20/18
to
Below code (after CODE BEGINS marker) compiles and runs successfully with gcc but
I would have expected an error.
See the part between the asterisks. Here we have an operation
returning AndSpecification<T> before AndSpecification<T> has been
fully defined. So I would expect the compiler to complain because
it doesn't know (at this stage) that first and second are members
of AndSpecification<T>
I don't know why the compiler accepts the code between the asterisks.
I would have said it's an error because AndSpecification<T> has not
been fully defined so we don't know what return {first, second} means.

Many thanks for your help,

Paul
***************************************************************************
template <typename T> AndSpecification<T> operator&&
(const Specification<T>& first, const Specification<T>& second)
{
return { first, second };
}
*************************************************************************


CODE BEGINS

// open closed principle

// open for extension, closed for modification

#include <string>
#include <vector>
#include <iostream>
using namespace std;

enum class Color { red, green, blue };
enum class Size { small, medium, large };

struct Product
{
string name;
Color color;
Size size;
};

struct ProductFilter
{
typedef vector<Product*> Items;

Items by_color(Items items, const Color color)
{
Items result;
for (auto& i : items)
if (i->color == color)
result.push_back(i);
return result;
}

Items by_size(Items items, const Size size)
{
Items result;
for (auto& i : items)
if (i->size == size)
result.push_back(i);
return result;
}

Items by_size_and_color(Items items, const Size size, const Color color)
{
Items result;
for (auto& i : items)
if (i->size == size && i->color == color)
result.push_back(i);
return result;
}
};

template <typename T> struct AndSpecification;

template <typename T> struct Specification
{
virtual ~Specification() = default;
virtual bool is_satisfied(T* item) const = 0;

// new: breaks OCP if you add it post-hoc
/*AndSpecification<T> operator&&(Specification<T>&& other)
{
return AndSpecification<T>(*this, other);
}*/
};

// new:
template <typename T> AndSpecification<T> operator&&
(const Specification<T>& first, const Specification<T>& second)
{
return { first, second };
}

template <typename T> struct Filter
{
virtual vector<T*> filter(vector<T*> items,
Specification<T>& spec) = 0;
};

struct BetterFilter : Filter<Product>
{
vector<Product*> filter(vector<Product*> items,
Specification<Product> &spec) override
{
vector<Product*> result;
for (auto& p : items)
if (spec.is_satisfied(p))
result.push_back(p);
return result;
}
};

struct ColorSpecification : Specification<Product>
{
Color color;

ColorSpecification(Color color) : color(color) {}

bool is_satisfied(Product *item) const override {
return item->color == color;
}
};

struct SizeSpecification : Specification<Product>
{
Size size;

explicit SizeSpecification(const Size size)
: size{ size }
{
}


bool is_satisfied(Product* item) const override {
return item->size == size;
}
};

template <typename T> struct AndSpecification : Specification<T>
{
const Specification<T>& first;
const Specification<T>& second;

AndSpecification(const Specification<T>& first, const Specification<T>& second)
: first(first), second(second) {}

bool is_satisfied(T *item) const override {
return first.is_satisfied(item) && second.is_satisfied(item);
}
};


int main()
{
Product apple{"Apple", Color::green, Size::small};
Product tree{"Tree", Color::green, Size::large};
Product house{"House", Color::blue, Size::large};

const vector<Product*> all { &apple, &tree, &house };

BetterFilter bf;
ColorSpecification green(Color::green);
auto green_things = bf.filter(all, green);
for (auto& x : green_things)
cout << x->name << " is green\n";


SizeSpecification large(Size::large);
AndSpecification<Product> green_and_large(green, large);

//auto big_green_things = bf.filter(all, green_and_large);

// use the operator instead (same for || etc.)
auto spec = green && large;
for (auto& x : bf.filter(all, spec))
cout << x->name << " is green and large\n";

// warning: the following will compile but will NOT work
// auto spec2 = SizeSpecification{Size::large} &&
// ColorSpecification{Color::blue};

getchar();
return 0;
}

Öö Tiib

unread,
Oct 20, 2018, 7:14:16 AM10/20/18
to
On Saturday, 20 October 2018 13:38:40 UTC+3, Paul wrote:
> Below code (after CODE BEGINS marker) compiles and runs successfully with gcc but
> I would have expected an error.
> See the part between the asterisks. Here we have an operation
> returning AndSpecification<T> before AndSpecification<T> has been
> fully defined. So I would expect the compiler to complain because
> it doesn't know (at this stage) that first and second are members
> of AndSpecification<T>
> I don't know why the compiler accepts the code between the asterisks.
> I would have said it's an error because AndSpecification<T> has not
> been fully defined so we don't know what return {first, second} means.
>
> Many thanks for your help,

With templates it does not matter so much where it was declared or
where it was defined. More important is that it is defined at place
where it is instantiated. Template of operator&& is instantiated
where it is called. If it is called nowhere then even syntax errors
in its definition may compile. Standard says ill-formed, no diagnostics
required.

Similar are references or pointers to some type. These can be
declared to incomplete type. Complete type is required where such
reference is used or pointer is dereferenced.

That makes programming in C++ not linear process but also it gives
plenty of ways to break out of circular references between types
and functions without sacrificing any type-safety.

Alf P. Steinbach

unread,
Oct 20, 2018, 12:04:30 PM10/20/18
to
On 20.10.2018 13:14, Öö Tiib wrote:
> [snip]
> With templates it does not matter so much where it was declared or
> where it was defined. More important is that it is defined at place
> where it is instantiated. Template of operator&& is instantiated
> where it is called. If it is called nowhere then even syntax errors
> in its definition may compile. Standard says ill-formed, no diagnostics
> required.
>
> Similar are references or pointers to some type. These can be
> declared to incomplete type. Complete type is required where such
> reference is used or pointer is dereferenced.
>
> That makes programming in C++ not linear process but also it gives
> plenty of ways to break out of circular references between types
> and functions without sacrificing any type-safety.

Also, both beginners and more experienced C++ programmers may guess
wrong about where the first diagnostic, if any, in this code occurs:

struct Gah;

auto foo() -> Gah;

void bar( Gah&& );

auto main()
-> int
{ bar( foo() ); }

I think that's nice.

In a way.


Cheers!,

- Alf
0 new messages