microsoft / GSL

Guidelines Support Library
Other
6.18k stars 740 forks source link

array_view not possible with incomplete Classes #135

Closed nolange closed 9 years ago

nolange commented 9 years ago

Hello,

I use a small simple class that takes a pointer and a size for wrapping normal arrays, quite the same what array_view is designed to solve in a much more generic fashion. I found a drawback compared to using a simple class, and that is that array_view does not work with incomplete classes. I dont know if its possible to fix this via templates specialization and sfinae voodoo, but would be really nice if this use-case is covered aswell

#include <array_view.h>
class CIncomplete;

void foo(gsl::array_view<CIncomplete> s); // OK
gsl::array_view<CIncomplete> s; // Not OK (says clang 3.6 and gcc 5.1 )
gdr-at-ms commented 9 years ago

Could you give a list of the operations that should (or should not) work for array_view?

nolange commented 9 years ago

Anything that is not dependent on the size of the class. Assume a trivial implementation of a pointer and size, take array_ref as example. What I require is typically giving a class an array to work with (no dynamic allocation allowed), which would mean the constructors taking a pointer and length (count of elements) should work. I also use data() a few times with incomplete classes,

Ideally any operation that doesnt iterate or measure distance should work with incomplete types.

gdr-at-ms commented 9 years ago

The indexing operator (aka 'operator[]') needs to be defined and it has to be member -- the whole point of array_view is to provide checked indexing. That needs the size of the element type. If indexing isn't needed, then is array_view still the right tool for your needs?

nolange commented 9 years ago

You misunderstood me. Sure, if iterators are incremented you need the type-size, if you use operator[] you need the typesize. What I want is to "prototype" an array_view. If this code compiles

class Incomplete;
struct A {
Incomplete _ptr;
unsigned _size;
};
A s_Instance;

then the replacement

class Incomplete;
struct A {
array_view<Incomplete> _array;
};
A s_Instance;

should compile aswell. The same thing works with unique_ptr aswell. Only if you call operations using the size (destructor in case of unique_ptr) you should need the definition of the class

(I can give you better and tested codesamples later)

gdr-at-ms commented 9 years ago

I think I understood what you were saying. In your code example, you don't appear to use or to need the indexing operator -- the raison d'etre of array_view. My question to you is why it is still the right tool? It can't be just because it is a pair of pointer and length.

nolange commented 9 years ago

No, thats exactly the reason, its a wrapper around a C-Style Array with optional range checks. Thats is what I want. The issue is that I have a definition of a class using some special kind of resources. I dont want to include the header for the resources, so I prototype the resource-class (I can explain the rationale if you are interested). If I instantiate the class I pass a pointer + size to it (or a wrapper like array_ref containing those), exactly this doesnt work with array_view.

I need the definition of the resources only in the implementation of the class (which uses iteration and stl algorithms), I dont want it to be included anywhere else.

I dont use the heap, since some of these resources will end in fixed memory addresses, and I am not allowed to anyway. With the pointer+size way the class it totally separated from the memory management, and I can place it on the stack, globally (in some instances it even gets initialized by the compiler and ends up in read-only memory) or with any kind of allocation.

I really dont know the exact solution that you want to archive with array_view, I need something like array_ref: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3334.html (I thought array_view is supposed to replace it) Unless array_view can deal better with incomplete types (again, I dont expect iteration or indexing to miraculously work in that case), it wont replace some uses a simple pointer + size abstraction can provide.

If this still isnt clear I can cook up a few files illustrating the use.

gdr-at-ms commented 9 years ago

array_view's indexing checking isn't optional. It is an integral part of why it is there. You can't index into an array of incomplete type. You can form the array_view at the point where the element type becomes complete. It is cheap to form on the fly.

nolange commented 9 years ago

The mere reason to use a wrapper is to simplify code and bundle information that belongs together. I could with that very same argument just say that I can form an unique_ptr just at the time I want a resource deleted. I hope you agree that this defeats the purpose.

For my example, consider all the different way an array_view could be initialized, and the work thats necessary to replicate in every class that want the same options. I made an example with 5 different ways to initialize the ptr/size tuple (also consider that constexpr is a rather great thing, so pushing initialization in the .cpp is not an option for me). Consider an more elaborate example that has 3 ways for initializing the rest of the class - thats 3 x 5 constructor, consider a class with 2 such arrays - 5 x 5 constructors. Copying code around scales badly.

class Inc;
struct A
{
   Inc *_ptr;
   unsigned _size;

   typename<N>
   constexpr A(Inc (&arr)[N]) {} // C-array
   typename<N>
   constexpr A(std::array<Inc,N>) {} // std::array
   typename<T, ...sfinae..magic... >
   constexpr A(const T &) {}// begin(), end() magix
   constexpr A(Inc *p, unsigned s) {} // ptr, size
};

struct B
{
   array_view<Inv> _array;

   constexpr B(array_view<Inv> a) {} // all-you-need
};

I really dont know how hard it is to make array_view partially usable for incomplete types, but for me this would be an important requirement. maybe its easier to also provide an simpler array_ref like in the proposal, but as far as I see only array_view is getting into the standard. Preferable array_view should be versatile enough to cover every use

gdr-at-ms commented 9 years ago

I am not sure I understand the comment about unique_ptr.

gdr-at-ms commented 9 years ago

What I meant by formed cheaply is that if you have

void f(array_view<Incomplete>);

you can just say

f({ ptr, length});

at the call site where you have the information.

nolange commented 9 years ago

This is not the issue, the class wont compile with a array_view member is

gdr-at-ms commented 9 years ago

I understand that. I don't know it is reasonable to demand that (1) safe checking for indexing (2) hide information necessary to request that facility (3) refuse to delay the request at the point where it makes most sense.

I see this as a possible flaw in the use scenario.

nolange commented 9 years ago

Index checking is done when... Indexing, what checks should there be in the constructor? I have a class that does pretty much all of this.

As said, just imagine you couldnt define a class using a unique_ptr. Not even to pass it around as reference. Yet it still works, only if the class is deleted the interior needs to be a complete class.

gdr-at-ms commented 9 years ago

As I indicated earlier, the indexing operator needs to be declared as a member of the class array_view. That cannot be declared only when the element type is known to be complete or used. That indexing operator is part of the interface array_view. I am sure you are not proposing an interface that can't be satisfied.

nolange commented 9 years ago

The operator is not the issue it wont compile (it was another function, I think tobytes). I told you before I dont know if its easy or feasible to do with a rather complicated class like array_view. If this is hard to do, I would accept this as answer - but so far we are getting nowhere. Working with incomplete classes would be a nice property, and open up further usecases like mine

And It works as intended with my implementation, which ressembles array_ref, so its not an "interface that cant be satisfied". Its technically possible and quite easy with simple classes.

gdr-at-ms commented 9 years ago

You are looking at how a particular implementation of a particular operation works today. I am looking at the whole interface of array_view. The fact it is to_bytes and not directly the indexing operator that is not compiling is not the fundamental point. Implementation comes after design. The real question is whether it makes sense to offer array index checking when the element type is incomplete?

We don't want just an implementation that happen to "work". We want a specification with understood semantics.

nolange commented 9 years ago

Im not talking plainly about the current implementation, but also the design. Neither is fixed as far as I know, but if you believe this is the wrong place to discuss this, then please point me in the right direction.

Just want to hold by the facts that, what I want is something thats valid and enforced by the standard, namely that incomplete types can be used, and that template methods arent instantiated until used. Means your operator[] wont cause trouble, and neither is the index-checking relevant unless you call functions that are dependend on the size of the type. It wont go against range checking at all. I strongly believe this to be possible and a worthy design consideration (mainly because I want someone else to write and maintain the code I am using, harrr) and dont think its too much work. As said, it would then be a full replacement for array_ref and my own implementation of a similar type, thus fit for more people which is what GSL should aim at.

I dont know where the interface of array_view is described and maintained, it might be that there is a conflict with the design somewhere that wouldnt allow a pointer to an incomplete type - range checking in general isnt the concern. I could imagine it could be a hassle getting all the different constructors working correctly. But as you said, we shouldnt hold onto a particular implementation of today.

nolange commented 9 years ago

Did some tests at home, seems like as_bytes and as_writeable_bytes are the problem, as here the function signature (return) depends on the Element size. Cant figure out a solution for that, except moving those function out-of-class.

With those two functions disabled, the following code will compile, the advantage is that you can declare classes with array_view members, even initialize and copy them (you only need the size of array_view itself for that). Only if you want to acces the contents of the array_view you need the definition of the CIncomplete class.

#include <array_view.h>
class CIncomplete;

struct A
{
    gsl::array_view<CIncomplete> _array;
    unsigned somefunctiondoingcheckedaccessdefinedelsewhere();
};

/* Supposedly extern functions */
CIncomplete* getInstances(unsigned n) { return nullptr; }
gsl::array_view<CIncomplete> getInstancesWrap(unsigned n) { return gsl::array_view<CIncomplete>(nullptr, n); }

A create(unsigned n)
{
    return A{{getInstances(n), n}};
}

A createWrap(unsigned n)
{
    return A{getInstancesWrap(n)};
}

const A s_StaticInstance {getInstancesWrap(6)};

int main()
{
    unsigned garbage[32];
    CIncomplete *pDangle = reinterpret_cast<CIncomplete *>(garbage);
    gsl::array_view<CIncomplete> array;
    gsl::array_view<CIncomplete> array2(pDangle, 4);

    CIncomplete *ptr = array.data();
    const auto len = array.length();
    array = array2;
}

void fail()
{
    gsl::array_view<CIncomplete> array;
//    array.begin(); /* probably not possible, but would be nice */
//    array.bytes(); /* needs size */
//    array[1]; /* needs size */
}

I hope I finally made myself clear, can we discuss the ups and downs now? To me, moving the 2 as_bytes functions out makes the array_view template more versatile.

mattnewport commented 9 years ago

The use case makes sense to me. There are a number of reasons for not wanting to include the definition for CIncomplete in the header with the definition of A and it does seem desirable to enable class members of array_views of incomplete types if possible.

If the only obstacle to this is that as_bytes and as_writeable_bytes are members and making them free functions would solve the problem then that sounds like a good idea but there may be reasons that the problem is more complex than that.

gdr-at-ms commented 9 years ago

I don't understand what you mean by "template methods arent instantiated until used".

The indexing operator isn't a "template method". It is a member function of a class template, but it isn't a template itself. It's declaration is always instantiated as the part of the class definition, even if it isn't used or called. After all, it is part of the class's interface!

to_bytes and consort are secondary to the point. If we ignore those functions for a moment, the central question is: does it make sense to have a view into an array of incomplete type?

I suspect the answer must make us pose. What does it mean to an array of incomplete type, and what does it mean to have a view into such array?
I don't think the answer is "because I want to hide the type definition". Because you can't keep the element type incomplete and still index into the array. So, at the place where it is meaningful to index into the array (and therefore have an array_view), you must have the complete type.

nolange commented 9 years ago

If the only obstacle to this is that as_bytes and as_writeable_bytes are members and making them free functions would solve the problem then that sounds like a good idea but there may be reasons that the problem is more complex than that.

Whats the worst that could happen, other than the compiler screaming (what are tests for) ? =) For 1 dimensional arrays it should be no problem, higher dimensions probably calculate stride-size but should be doable too. Whatever you dont use shouldnt cause problems, if you use something that needs the definition then you will need to include the definion of the inner class. If copy,construct,data(),length() are required to work with incomplete types, then a simple test will fail to compile if anything prevents this.

The indexing operator isn't a "template method". It is a member function of a class template, but it isn't a template itself. It's declaration is always instantiated as the part of the class definition, even if it isn't used or called. After all, it is part of the class's interface!

Ok, member function of a template. But what the standard says, is that the declaration (method signature) is instantiated with the class, but not the definition. If the size of T is used as method argument or return value then it wont compile (see as_bytes methods). If its used in the methods body then it wont be instantiated until the function is called (or the address taken AFAIK).

to_bytes and consort are secondary to the point. If we ignore those functions for a moment, the central question is: does it make sense to have a view into an array of incomplete type?

They do for me, I use something similar in a rather big project liberally. You could aswell ask why incomplete types are allowed at all - its to define types which contain them and generate some basic properties. I posted an example above, I have code that manage the containing types and it doesnt care what the type itself does with the incomplete inner type. Take unique_ptr as another example, does it make sense to have a unique_ptr to a type you dont know how to delete? Does it make sense to use a linked list with an incomplete type?

I don't think the answer is "because I want to hide the type definition". Because you can't keep the element type incomplete and still index into the array. So, at the place where it is meaningful to index into the array (and therefore have an array_view), you must have the complete type.

Yes, but that is at exacly one place if the incomplete type is an implementation detail of that class. I dont need that information if I want to copy arround the array_view, the class itself or access any method of the class. If I dont need it, I dont want to include it but just add an prototype.

mattnewport commented 9 years ago

Another way to look at it is to ask, should I be able to replace any use of a {pointer, size} pair in existing code that is used as a view onto contiguous data with an array_view? Naively it seems like the answer should be yes. In this case however, where a class has {pointer, size} members where the pointer is to an incomplete type, array_view is not a drop-in replacement.

If this is a special case where an array_view is not the right replacement, what should be used instead? Do users just have to stick to a raw pointer and size in this case as far as GSL / the standard is concerned?

gdr-at-ms commented 9 years ago

@mattnewport array_view isn't just a pointer+length pair; that is the point made earlier. If you don't have any semantics associated with that pair, then you probably don't need array_view.

gdr-at-ms commented 9 years ago

@nolange You want the interface to be subservient to the implementation. That is backward.

nolange commented 9 years ago

I dont use it just as pointer+length pair. But I differentiate between the header file which provides a definition of a class constaining an array_view, which should be enough to construct and copy an instance (ideally both constexpr) and define the methods of the class.

In the implementation of the class the inner type is not incomplete and the features of array_view are used to implements the methods of the class. I thought its common sense that incomplete types are used fully defined at some place, otherwise why not just use void * ?

this pointer+length pair represents an array (of arbitrary but fixed size after init), allows iteration and passing such instances arround "as whole" instead of two values that are mutually useless without the other.

The other approach I could take is the pimple idiom for the header, but this requires allocations (not good in realtime or safety critical apps much less one that requires both) and prevents free placement of the instances. For example placing an array on the stack, followed by an class that uses this array. Or even placing everything in read-only memory since the compiler could "constexpr everything".

I already do this, with a own small class, and I wont ever switch to array_view in this project. Its just that a relatively simple change could add several new nice properties to array_view. (And unless the code at work I could use array_view everywhere)

mattnewport commented 9 years ago

@gdr-at-ms It seems to me that having an array_view as a member of a class is clearly a valid use case when it captures the semantics correctly (a non-owning view on contiguous data). If you are using a member {pointer, size} pair in this way are you suggesting this is not a valid use of array_view?

If it is a valid use, shouldn't array_view be a valid replacement in as many situations as possible, including where the pointed to type is incomplete at the point of declaration of the member?

If it's not a valid use what do you suggest as an alternative?

nolange commented 9 years ago

You want the interface to be subservient to the implementation. That is backward.

Sorry, are you kidding me? Read up about incomplete types, read up why unique_ptr (the interface) does exactly specify what operations require the full type. I brought this up not just once

I want the interface modified, and the implementation to follow.

gdr-at-ms commented 9 years ago

@mattnewport I didn't see any argument that array_view shouldn't be used as data member. The question is whether that should be reason to be free of the semantics of an array view. If all you need at the declaration site is a pointer and a length, then you probably don't need an array_view for that. You want an array_view to capture and state a semantics beyond pointer and length.

If implementation constraints forces you to hide some information, then it is only natural to state the sequenced semantics at the place where you need it. I don't see that being unreasonable.

gdr-at-ms commented 9 years ago

@nolange I am still lost at the relevance of unique_ptr.

gdr-at-ms commented 9 years ago

@nolange I would like to see a strong case for that modification.

nolange commented 9 years ago

unique_ptr is similar as it allows you to define a class owning one to be defined with an incomplete inner type. its an example where this is stated in the documentation (interface), how this allows functions to pass those unique_ptr around per reference with incomplete types and how the standards body found this important enough to make it an requirement.

Any reason for this is a reason to allow array_view to support definitions with incomplete types

You get:

You lose:

gdr-at-ms commented 9 years ago

Ah, but I don't see the standard specification unique_ptr as a good template to design array_view after.

mattnewport commented 9 years ago

This code works for std::unique_ptr (and it is very good that it does). Why should array_view not also work the way the minimal array_view here does?

#include <iostream>
#include <memory>

// In a header
struct Foo;

template<typename T>
struct array_view {
    T* ptr;
    size_t size;
    T& operator[](size_t i) { return ptr[i]; }
};

struct Bar {
    std::unique_ptr<Foo[]> myFoo;
};

struct Baz {
    array_view<Foo> fooView;
};

// In a .cpp

struct Foo { int i = 0; };

int main() {
    Bar bar{{std::make_unique<Foo[]>(10)}};
    Baz baz{{bar.myFoo.get(), 5}};
    std::cout << baz.fooView[3].i << std::endl;
}
gdr-at-ms commented 9 years ago

@mattnewport The question isn't whether the code works. The question is what the interface and semantics should be.

mattnewport commented 9 years ago

I don't see how this impacts interface or semantics. It's really a question of physical design, exactly as with unique_ptr. It's desirable both for compile times and for code organization purposes to not have to expose the definition of types in headers that don't strictly need them. In a language with a different compilation model to C++ this wouldn't really be an issue.

nolange commented 9 years ago

You asked for a usecase, you got a few.

semantics should be that

should work with incomplete types (they are just copying pointer / size, so why shouldnt they). a constructor with start/end iterators wont be possible to be called with an incomplete type

mattnewport commented 9 years ago

As a side note, thinking about it some more it seems that from an interface and semantics point of view, as_bytes() and as_writeable_bytes() would be better as free functions anyway.

gdr-at-ms commented 9 years ago

@mattnewport Making as_bytes() and consort non-member function is a sensible design choice. That does mean you get array view of incomplete types, just because the implementation happens to allow it though :-)

mattnewport commented 9 years ago

@gdr-at-ms I really think this should be a requirement as it is for unique_ptr, assuming as_bytes() and friends are made non-members, not just something that 'happens to work' on some implementations.

neilmacintosh commented 9 years ago

I just want to restate a point @gdr-at-ms is making here consistently: what is the useful purpose of having an array_view member visible with an incomplete type? All I have seen so far is code snippets that illustrate what you want to achieve - which I think is well understood. Where is a code example that illustrates the need for this behavior?

mattnewport commented 9 years ago

@neilmacintosh I thought we'd been fairly clear that the need is the same as the need with unique_ptr.

There are two main reasons it's desirable. One is for compile times - if the incomplete type is large and complex we don't want to have to drag in its definition merely because the type with an array_view or unique_ptr member happens to use it as an implementation detail. The other is for information hiding. If the incomplete type is purely an implementation detail then it can be defined in a .cpp and not exposed via a public header to the client of the code.

It's the same concerns that lie behind the pimpl idiom, only you shouldn't be forced to pay the dynamic allocation and code complexity overhead of the pimpl idiom in simple cases where it shouldn't be necessary to achieve the compile time and information hiding goals.

neilmacintosh commented 9 years ago

So that leaves me still slightly puzzled. array_view is a view over someone else's storage. So your "class-with-an-array-view-member" wants to have a member that refers to someone else's storage. Fair enough, but in that case it must need to receive that array_view over someone else's storage somehow...which suggests a caller that can see the full definition of the type.

So why would hiding the definition help, given it needs to be visible to both caller and the class holding the array_view?

This is why I'm asking for a concrete code example, that demonstrates the real need for this behavior.

mattnewport commented 9 years ago

@neilmacintosh Here's an example of the kind of code organization that should be possible. This is something of an artificial example but I hope it makes the point clearly enough that there is a real valid use case for this.

#include <algorithm>
#include <iostream>
#include <memory>
#include <numeric>

// Dummy array_view - would like to use the real one
template<typename T>
struct array_view {
    T* ptr;
    size_t size;
    T& operator[](size_t i) { return ptr[i]; }
};

// In a header
struct Foo;

struct Bar {
    std::unique_ptr<Foo[]> myFoo;
};

struct Baz {
    array_view<Foo> fooView;
    int sumOfFoos() const;
};

// Client code in a .cpp - doesn't need to see Foo definition

int sumOfFoos(const Baz& x) { return x.sumOfFoos(); }

// Implementation in another .cpp, Foo definition is completly private

using namespace std;

struct Foo { 
    int i = 0; 
    Foo& operator++() { ++i; return *this; }
};

Foo operator+(Foo a, Foo b) {
    return Foo{a.i + b.i};
}

int Baz::sumOfFoos() const {
    return accumulate(fooView.ptr, fooView.ptr + fooView.size, Foo{0}).i;
}

// Another .cpp

int main() {
    constexpr auto sz = 10;
    Bar bar{{std::make_unique<Foo[]>(sz)}};
    iota(bar.myFoo.get(), bar.myFoo.get() + sz, Foo{0});
    Baz baz{{bar.myFoo.get(), 5}};
    std::cout << sumOfFoos(baz) << std::endl;
}
nolange commented 9 years ago

@neil: Its real hard to find a "real need", unless you define whats meant by that. I can implement everything in a dozend other ways< with different srawbacks. You got an example already (getInstancesWrap), even if this is just schemed and not with all the bits implemented and the cpp files ommitted. How much more code you need?

Whats with some kind of elegance, that you only require of the types whats absolutely needed. This for me is a pretty important design decision to ensure its value is maximized. There is no reason an array_view couldbe be a copyable type with the pointer type beein incomplete.

even if array_view is a view over someone elses storage, I dont get why the caller needs to have the full definition. Its rather narrow minded to assume every class manages their own storage. You might aswell have a phase where you call some factory (no definiton of the types available, see the example with getInstancesWrap), then instanciate all your worker classes with those results (no definition of the types available, just fill in the pointers), then use the worker classes (no definition of the types available, just the classes definition). Only the factory any the worker classes need their respective types defined, and that only for the implementation.

But well, consider a class managing logs, having a fixed amount of entries. I dont want allocations unless they are unavoidable (actually I have to porve I dont use them), and implementing such classes just means you "allocate" before the instance lives, clean up after it died.

logprinter.h:

class CLogEntry;
class CLogprinter
{
  void printLogs(array_view<CLogEntry>);
}

logprinter.cpp:

#include <logprinter.h>
#include <logentry.h>
   void CLogprinter::printLogs(array_view<CLogEntry>) {..}

myLog.h:

class CLogEntry;
class CLog
{
  CLog(array_view<CLogEntry> array) : _array(array) {}
...
   void logEntry(const char *msg);
   array_view<CLogEntry> getEntries();

...
  array_view<CLogEntry> _array;
  unsigned _filled;

  static CLog _instance;
}

myLog.cpp:

#include <myLog.h>
#include <logentry.h>
   void CLog::logEntry(const char *msg) {..}
   /* return the added Entries */
   array_view<CLogEntry> CLog::getEntries() {...}

mysystem.cpp:

#include <myLog.h>
#include <logentry.h>
/* "factory" for embedded system*/
CLogEntry s_Table[100];
/* (already initialized by the compiler) */
CLog CLog::_instance{array_view<CLogEntry>(s_Table, 100)};

someplace_else.cpp:

#include <logprinter.h>
#include <myLog.h>
CLogprinter print;
CLog::_instance.logEntry("Donuts exhausted");
print.printLogs(CLog::_instance.getEntries());

Note that you can get, sink, and pass around subsections of the CLogprinter array without knowing the implementation,

gdr-at-ms commented 9 years ago

When considering "absolutely minimal requirements", one has to understand "semantics requirements".

Indexing is fundamental to the semantics of array_view, and that requires completeness of the element type. We understand the language as it is defined today and how to write codes that closely come to individual rules (that some of us may have authored in the standards text.) But that is not we are doing here. We want to bring a certain set of guaranteed properties and promote good C++; in return we require a certain set of semantics guarantee. Just being able to accommodate some nice construct isn't the goal, nor necessarily a good design guide.

I personally don't think requiring a completeness of the element type is a hardship. I will even go as far as conjecturing that the component will gain in clarity about what is assumed and what is needed. "convenience" isn't always what it seems to be.

mattnewport commented 9 years ago

@gdr-at-ms The issue is not one of convenience here. It's one of good physical design which is very important for build times (maybe this will be less of an issue once we get modules but it won't entirely go away) and also for information hiding. Would you not agree that it is desirable for clients to not be exposed to implementation details that shouldn't concern them, like the definition of the incomplete class type here?

nolange commented 9 years ago

I really believe we have a misunderstanding of incomplete. What I want is a functionality similar to using prototypes (those are incomplete types) instead of having to use the full definition everywhere.

You dont need to know the definition of a type to pass around a pointer, an array_view should be the same - unless there really are good reasons against it. In many cases the state of an array_view is a pointer with size information, what else you can do with it (the safe indexing operations) is not the subject here.

gdr-at-ms commented 9 years ago

I don't know what you mean by "physical design". We see the types we propose as having (logical) semantics. The concrete implementation is secondary to the semantics.

The requirement of complete element type isn't against "information hiding". You can still accomplish good encapsulation while meeting that requirement, as I've shown several messages back.

gdr-at-ms commented 9 years ago

If all you want is pass a pointer around (without revealing what it is about), you probably want to say that directly in code using any of the existing pointer types, instead of weakening the semantics of array_view. That expresses the intended use directly in the code, instead of remaining an emergent design.

neilmacintosh commented 9 years ago

Thanks both @nolange and @mattnewport for your examples. The reason I wanted to see these is it helps me understand the problem you are trying to solve. I find generalized arguments about how elegance or just "why not, it could be" much less helpful.

With the examples, I still don't see the necessity of allowing array_view to work with incomplete types. The logging example, I think it is clearest that one of @gdr-at-ms 's comments applies: you are not using the array_view semantics in the interface of the CLog class that is in mylog.h (with the exception of getEntries), so you could equally just store a pointer and length and then construct array_view instances on the fly in mylog.cpp when you need to operate over that data safely.

So let's consider the CLog::getEntries() member function. I assume this is public (if it is not, then array_view really doesn't need to be part of the interface of CLog at all)...it returns an array_view. Anyone who includes the .h would have a reasonable expectation of being able to call the function and use the returned array_view. As soon as they try to use that array_view, they require the full definition of CLog.

The arguments here appear to focus on build times and physical code organization. I am sympathetic to problems of build time and physical organization of source code...but there are other, far better ways to resolve those issues actively being discussed for the language. I would recommend looking at the modules proposal and talk from @gdr-at-ms as an excellent example.