1

EDIT:

  1. In addition to the "difficulty" of the size, my overload would have to take precedence over the built-in overload, which prints the address of the first element... as per this, that is not possible, so I am not sure what I want to do is possible, anyway. At least with the proposed syntax below.
  2. Ultimately, I mean to template the overloaded operator as template <class T> std::ostream& operator<<(std::ostream& os, const T* arr) or the like, and explicitly instantiate it for several types (int, char, float, double, etc.), so I have a uniform format.

I usually overload operator<< for vector<T> and other STL containers. That can easily be done since the objects know their size.

Is there a way to overload operator<< for an array of, say, doubles? On one hand, it seems one would need to pass the size somehow (it is unknown at compile time). On the other hand, there seems to be no mechanism for this (AFAIK... I leave it for the knowledgeable to answer).

I.e., I want

int n = 10;
double * pd1 = new double[n];
double pd2[n] = {};
...
cout << pd1 << endl;
cout << pd2 << endl;

I know I can define my custom print, but I want to know if I can work the <<-way.

  • For stack allocated array you could [pass it by reference](https://stackoverflow.com/questions/10007986/c-pass-an-array-by-reference). For dynamically allocated one... [This question](https://stackoverflow.com/questions/66690939/is-it-possible-to-pass-a-dynamically-allocated-array-to-a-function-that-requires/66691122#66691122) was asked today, it may be of use (if the array size is known at compile time). – Yksisarvinen Mar 18 '21 at 20:49
  • Does the syntax have to be `cout << pd1 << endl;`? Would you accept `cout << {pd1 + size_of_pd1} << endl;`? – NathanOliver Mar 18 '21 at 20:51
  • If the C++20 feature std::span is available this could be the solution to your problem. – Andreas H. Mar 18 '21 at 21:03
  • For an actual array you can do it with a template function. For a pointer I don't think it's possible. – Mark Ransom Mar 18 '21 at 21:07
  • @Yksisarvinen - The objective of my overloads is to deal with generic arrays, i.e., with unknown size at compile time. With this clarified, I am not sure if your answer to the question would be "Yes, it can be done" or "No, it can't be done". – sancho.s ReinstateMonicaCellio Mar 18 '21 at 21:17
  • @NathanOliver - Not exactly what I am looking for, but I guess it is a step forward compared to the complete lack of the capability in the standard. I welcome your ideas. – sancho.s ReinstateMonicaCellio Mar 18 '21 at 21:28
  • It's not really the standard that's lacking. For the case of `pd2` you could write an function like `template std::ostream& operator <<(std::ostream&, T(&array_name)[N])`. The standard doesn't do this just like for vector. For the pointer case, there is no way to get the size of the array it points to. That's just how the language works. The size of an array is part of its type, it's not stored anywhere. – NathanOliver Mar 18 '21 at 21:33
  • Your declaration of `pd2` will not compile unless you make n const. With `const int n = 10;` pd2 is treated as a fixed size stack array. (At least it "works on my machine" ;) ) – Andreas H. Mar 18 '21 at 21:33
  • @AndreasH. - I have actually compiled and executed it. – sancho.s ReinstateMonicaCellio Mar 18 '21 at 21:34
  • @sancho.sReinstateMonicaCellio If you used GCC, its a non-standard it has on by default. If you add `-pedantic-errors` to your compile options, it'll stop the code from compiling. – NathanOliver Mar 18 '21 at 21:35
  • @NathanOliver - And you would call your proposed function with the syntax of your first comment? That is worth an answer... – sancho.s ReinstateMonicaCellio Mar 18 '21 at 21:36
  • No, that function will only work for arrays declared at compile time, like `pd2`. There isn't a way AFAIK to get `cout << pd1 << endl;` to work. – NathanOliver Mar 18 '21 at 21:37
  • 1
    @NathanOliver: No, but if you wanted you could make `std::cout << print_array_size(n) << pd2;` work – Ben Voigt Mar 18 '21 at 21:38
  • @BenVoigt Oh, that's a neat idea. I've never considered doing it like that. I just wind up making a `print` function. – NathanOliver Mar 18 '21 at 21:39
  • @NathanOliver - Could you be more explicit on how would the function you suggest look like, and how you would call it? – sancho.s ReinstateMonicaCellio Mar 18 '21 at 21:39
  • @sancho.sReinstateMonicaCellio Just to be clear, you want what function I would write to get `cout << {pd1, size_of_pd1} << endl;` to compile? – NathanOliver Mar 18 '21 at 21:40
  • also I had a typo. It should have been `{pd1, size_of_pd1}` in my original comment, not `{pd1 + size_of_pd1} ` – NathanOliver Mar 18 '21 at 21:41
  • @NathanOliver - You made 2 suggestions: 1) It is possible to write an overload that would be called with `cout << {pd1, size_of_pd1} << endl;` (it looked weird to me with the typo...) Could you give more detail on the definition of such overload? 2) It is possible to write an overload with prototype `template std::ostream& operator <<(std::ostream&, T(&array_name)[N])`. Could you give more detail on the definition and usage of such overload? Thanks – sancho.s ReinstateMonicaCellio Mar 18 '21 at 21:43
  • @BenVoigt - Could you give more detail on the definition and usage of your proposed overload? – sancho.s ReinstateMonicaCellio Mar 18 '21 at 21:45
  • @MarkRansom - Could you give more detail on the definition and usage of your proposed overload? – sancho.s ReinstateMonicaCellio Mar 18 '21 at 21:45
  • 1
    @sancho.sReinstateMonicaCellio Interesting. I am using Visual Studio 2019 and the compiler refuses to declare `double v[n];` with just `int n = 5;`. (Says "Expression does not evaluate to a constant" ... free translation of the german error message) – Andreas H. Mar 18 '21 at 21:46
  • @sancho.sReinstateMonicaCellio: In mine, `print_array_size(int size)` would return a helper object, let's name that `array_print_params`. There would be a `array_print_stream operator<<(std::ostream&, array_print_params)` overload, that stores both reference to stream and to params in the `array_print_stream` object. That array_print_stream object would have `operator<<` defined for pointers, doing the array printing with the pointer operand, the stored ostream, and the stored size. And finally return the ostream reference again to permit chaining `<<` into the sunset. – Ben Voigt Mar 18 '21 at 23:15
  • My apologies. I thought it would be easy to make a template-based function work, but it seems decay-to-pointer always takes precedence. – Mark Ransom Mar 19 '21 at 03:30

2 Answers2

2

The following example allows you to use:

{
   int v[5]{1,2,3,4,5};
   std::cout << arrayView(v);

   int *v2 = new int[5];
   std::cout << arrayView(v2, 5);
}

by defining

#include <iostream>

template<class Type>
class ArrayView
{
public:
    const Type *const begin, *const end;
};

// creates an ArrayView from a pointer and the number of items
template<class Type>
ArrayView<Type> arrayView(const Type* ptr, size_t items)
{
    return { ptr, ptr + items };
}

// creates an ArrayView from a fixed size array
template<class Type, size_t Size>
ArrayView<Type> arrayView(Type (&ptr)[Size])
{
    return { ptr, ptr + Size };
}

// outputs an ArrayView
template<class Type>
std::ostream& operator<<(std::ostream& s, ArrayView<Type> v)
{
    for (auto it = v.begin; it != v.end; ++it)
        s << *it;
    return s;
}

int main(int argc, char** argv)
{
    double v1[] = { 1, 2, 3, 4 };
    double* v2 = new double[5];

    for (int i = 0; i < 5; ++i)
        v2[i] = i + 10.0;

    std::cout << arrayView(v1) << std::endl;

    std::cout << arrayView(v2, 5) << std::endl;

    return 0;
}

The arrayView template function initializes an instance of the ArrayView template class which carries pointer and size (in the form of begin and end pointer).

For fixed size arrays this is not as efficient as it could be. A more efficient solution would be to create a dedicated template which stores the array size within its type:

template<class Type, size_t Size>
class FixedArrayView
{
public:
    const Type* const begin;
};

// creates an ArrayView from a fixed size array
template<class Type, size_t Size>
FixedArrayView<Type, Size> fixedArrayView(Type(&ptr)[Size])
{
    return { ptr };
}

// outputs a FixedArrayView
template<class Type, size_t Size>
std::ostream& operator<<(std::ostream& s, FixedArrayView<Type, Size> v)
{
    for(size_t i = 0; i < Size; ++i)
        s << v.begin[i];
    return s;
}

This could also be done without the need of an extra template class

template<class Type, size_t Size>
std::ostream& operator<<(std::ostream& s, Type(&arr)[Size])
{
    for (size_t i = 0; i < Size; ++i)
        s << arr[i];
    return s;
}

which allows you to write

{
   int v[5]{1,2,3,4,5};
   std::cout << v;
}

But the latest template operator<< has a big problem which strikes you if you , for example, try to output some string literal with std::cout << "abc";

However, there is a solution using SFINAE to make this template not handle "const char" (for which there already is an operator<< defined by <iostream>):

template<class Type, size_t Size, typename std::enable_if<!std::is_same<Type, const char>::value>::type* = nullptr>
std::ostream& operator<<(std::ostream& s, Type(&ptr)[Size])
{
    for (size_t i = 0; i < Size; ++i)
        s << ptr[i];
    return s;
}
Andreas H.
  • 1,757
  • 12
  • 24
  • I will check this... does it require C++20, as mentioned in your comment? – sancho.s ReinstateMonicaCellio Mar 18 '21 at 21:33
  • 1
    FWIW, you've basically just reinvented `std::span`, which is a part of C++20. – NathanOliver Mar 18 '21 at 21:34
  • 1
    Not related to the question, but in your answer several times you have confused "static array size" with "array stored on the stack". One doesn't guarantee the other. – Ben Voigt Mar 18 '21 at 21:36
  • @sancho.sReinstateMonicaCellio It should work with classic C++ but I did not check it. I will include a better solution for stack arrays in a few seconds. – Andreas H. Mar 18 '21 at 21:36
  • @NathanOliver I am aware of std::span (as I wrote in the comment to the original question). But it is still not available in many compilers. – Andreas H. Mar 18 '21 at 21:40
  • @BenVoigt I am not completly sure about the differences. Is the size part of the type of a static sized array allocated on the heap? And is there a possibility to allocate a dynamic sized array on the stack? – Andreas H. Mar 18 '21 at 21:42
  • How would you explicitly instantiate the last version, and how would you call it? – sancho.s ReinstateMonicaCellio Mar 18 '21 at 22:02
  • @sancho.sReinstateMonicaCellio IMHO A full explicit instantiation does not make much sense but it can be done with `template<> std::ostream& operator<<(std::ostream& s, int* (&ptr)[4]) { ... }` for example. – Andreas H. Mar 18 '21 at 22:13
  • The point is that I mean to have all code related to the overload in a separate header/source file, the same as I do with my overloads for STL containers. – sancho.s ReinstateMonicaCellio Mar 18 '21 at 22:24
  • @sancho.sReinstateMonicaCellio This want be possible for the solution with the array reference. You would need an explicit instantiation for every possible value of Size. – Andreas H. Mar 18 '21 at 22:47
  • @sancho.sReinstateMonicaCellio I added a few usage examples and a template operator<< version that does not conflict with the standard library version of operator<< defined for string literals (const char *) – Andreas H. Mar 18 '21 at 22:54
  • Nice solution. I could make some things work. What I could not make work so far: 1) Place headers in a .h file and class/function definitions (plus explicit instantiations) in a source file. I could move `operator<<` to the .cc file. 2) Add, e.g., `os << "["`at the beginning of the output, together with the SFINAE-related function. This produces an ambiguity. – sancho.s ReinstateMonicaCellio Mar 18 '21 at 23:11
  • 1
    @AndreasH: Allocate dynamically sized array on the stack? Yes, with `alloca`, VLA (C only), "array of runtime bound" (proposed for C++, much like a C VLA). Allocate fixed-size array (with size carried in static type) outside the stack? Yes, with `static` local variable, global variable, member of a class and then put class instance outside the stack, etc. Very simple example: `struct S { int x[5]; }* p = new S(); std::cout << p->x;` – Ben Voigt Mar 18 '21 at 23:18
2

To get plain old array to with with cout, you can provide an overload like

template <typename T, std::size_t N, std::enable_if_t<!std::is_same_v<char, std::decay_t<T>>, bool> = true> 
// SFINAE needed here for char arrays like cout << " "
std::ostream& operator <<(std::ostream& os, T(&array)[N])
{
    for (const auto& elem : array)
        os << elem << " ";
    return os;
}

which allows you to write code like

int main()
{
    int arr[10]{1,2,3,4,5,6,7,8,9,10};
    std::cout << arr;
}

For an array that you allocate at run time, it gets a little trickier. Pointers in C++ know nothing about the size of the object they point to, or at least as far as the standard is concerned. That means there isn't a portable way to get the size of the array the pointer points to. To get around that, we can leverage std::span from C++20. If you don't have that you can write your own or get an open source version. Doing that gives you an overload like

template <typename T, std::size_t Extent> 
std::ostream& operator <<(std::ostream& os, std::span<T, Extent> array)
{
    for (const auto& elem : array)
        os << elem << " ";
    return os;
}

And then you would use it like

int main()
{
    int * p_arr = new int[10]{21,22,23,24,25,26,27,28,29,20};
    std::cout << std::span{p_arr, 10};
}

It's not exactly the syntax you are looking for, but it only requires you to write the one overload (and potentially span).

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • I wonder whether this allows for adding, e.g., `os << "["` at the beginning of the output, without producing an ambiguity. Plus, whether it works with a variable ǹ` instead of a fixed number `10`. – sancho.s ReinstateMonicaCellio Mar 18 '21 at 23:14
  • @sancho.sReinstateMonicaCellio In`int * p_arr = new int[10]{21,22,23,24,25,26,27,28,29,20};`, the `10` can be replaced with a variable that you get at run time. It just didn't do that in the example. As far as doing `os << "["`, I just updated the code in the answer with some SFINAE to get that to work. – NathanOliver Mar 18 '21 at 23:19