16

Is there any other way to receive a reference to an array from function returning except using a pointer?

Here is my code.

int ia[] = {1, 2, 3};
decltype(ia) &foo() {   // or, int (&foo())[3]
    return ia;
}

int main() {
    int *ip1 = foo();   // ok, and visit array by ip1[0] or *(ip1 + 0)
    auto ip2 = foo();   // ok, the type of ip2 is int *
    int ar[] = foo();   // error
    int ar[3] = foo();  // error
    return 0;
}

And a class version.

class A {
public:
    A() : ia{1, 2, 3} {}
    int (&foo())[3]{ return ia; }
private:
    int ia[3];
};

int main() {
    A a;
    auto i1 = a.foo();    // ok, type of i1 is int *, and visit array by i1[0]
    int i2[3] = a.foo();  // error
    return 0;
}

Note: const qualifier is omitted in code.

I know the name of the array is a pointer to the first element in that array, so using a pointer to receive is totally viable.

Sorry, I made a mistake. From Array to pointer decay

There is an implicit conversion from lvalues and rvalues of array type to rvalues of pointer type: it constructs a pointer to the first element of an array.

Please ignore that XD

I'm just curious about the question I asked at the beginning :)

Jaege
  • 1,833
  • 4
  • 18
  • 32
  • 2
    You cannot copy-initialize arrays. Returning references to arrays is a red herring; that is allowed and works fine. – Kerrek SB Dec 23 '15 at 15:52
  • 4
    name of the array is not a pointer to the first element, but can be implicitly converted to. There is a difference and this common mistake leads to errors. Anyway you better use `std::array` to avoid confusion. – Slava Dec 23 '15 at 15:55
  • 2
    *"I know the name of the array is a pointer to the first element in that array"* - this is wrong. Read [this](http://en.cppreference.com/w/cpp/language/array#Array_to_pointer_decay). EDIT: Ah, Slava beat me to it. The link is still a good read, though. – Not a real meerkat Dec 23 '15 at 15:57
  • Oh, you mean in the class version? I've compiled the code in VS2015 and no error occurred (except the line I commented error), then how can I initialize the array? @KerrekSB – Jaege Dec 23 '15 at 16:07
  • @Jaege: You can only copy-list-initialize, direct-list-initialize, value-initialize and default-initialize arrays, I think. – Kerrek SB Dec 23 '15 at 16:08
  • @KerrekSB I think the new feature *list initialization* in C++11 is what happened in the code. See: [answer here](http://stackoverflow.com/a/10694479/5340808). Also [list initialization](http://en.cppreference.com/w/cpp/language/list_initialization). – Jaege Dec 23 '15 at 16:19
  • @Jaege: Sorry, my comment was too unspecific. I meant specifically that `int a[3] = f();` is not valid. – Kerrek SB Dec 23 '15 at 16:30
  • @KerrekSB Oh yes, you're right. I got the point now :) – Jaege Dec 23 '15 at 16:33
  • Use `std::array` or `std::vector` – Basile Starynkevitch Jan 02 '16 at 09:39

5 Answers5

20

Is there any other way to receive a reference to an array from function returning except using a pointer?

Yes, using a reference to an array, like with any other type:

int (&ref)[3] = a.foo();

To avoid the clunky syntax, you could use a typedef instead.

typedef int int_array3[3];

...
int_array3& foo() { return ia; }

...

int_array3& ref = a.foo();
juanchopanza
  • 223,364
  • 34
  • 402
  • 480
13

You should use std::array to avoid confusion and make cleaner, safer and less clunky code:

class A {
public:
    typedef std::array<int,3> array;
    A() : ia{1, 2, 3} {}
    array &foo(){ return ia; }
private:
    array ia;
};

int main() {
    A a;
    auto i1 = a.foo();      // ok, type of i1 is A::array, it is a copy and visit array by i1[0]
    for ( int i : i1 ) {}   // you can iterate now, with C array you cannot anymore
    auto &i2 = a.foo();     // ok, type of i2 is A::array&, you can change original by i2[0] = 123 
    A::array i3 = a.foo();  // fine, i3 is now a copy and visit array by i3[0]
    A::array &i4 = a.foo(); // fine, you can change original by i4[0] = 123
    int *i5 = a.foo().data();  // if you want old way to pass to c function for example, it is safer you explicitly show your intention
    return 0;
}

I know the name of the array is a pointer to the first element in that array

This is incorrect, array can be implicitly converted to a pointer to the first element. It is not the same.

Jaege
  • 1,833
  • 4
  • 18
  • 32
Slava
  • 43,454
  • 1
  • 47
  • 90
  • 3
    This is quite misleading because it gives the impression that a reference to an array cannot be returned, or that the return value cannot be used. – juanchopanza Dec 23 '15 at 16:10
  • 2
    @juanchopanza Where did I say that? – Slava Dec 23 '15 at 16:13
  • 3
    I didn't mean that you say it. Just that by concentrating on an `std::array` solution without mentioning that it is perfectly fin to return and use a reference to an array this gives the impression that you can't do it. – juanchopanza Dec 23 '15 at 16:27
  • 3
    It is not perfectly fine to return reference to array, as this leads to poor and difficult to understand code. This is C++ first of all, C arrays are clunky as you mentioned in your answer – Slava Dec 23 '15 at 16:28
  • 1
    std::array is a complete native array encapsulation. It is difficult to use it on algorithms/functions declared with arguments as native array references. –  May 10 '18 at 05:26
  • @DusanJovanovic I hope you never create algorithms/functions that use native array reference. And if you see a such library - it is a good sign not to use it. – Slava May 10 '18 at 13:34
  • 2
    @Slava, no problem ... I might also suggest you send a proposal to ISO C++ committee to banish references to native arrays, too. If you are quick enough your proposal might become part of C++20. –  May 11 '18 at 09:55
  • @Slava, contrary to your code comment, one can iterate over a native array, if we understood you right? https://wandbox.org/permlink/uY1Bf4z8gQF0BAWo –  May 11 '18 at 10:04
  • @DusanJovanovic I did not say it must be removed from the language. But I cannot imagine a good design that would only accept native array by reference. – Slava May 11 '18 at 13:49
  • @DusanJovanovic I repeat bad design is when a function(s) accepts **ONLY** native array by reference. What is not clear in my statement? – Slava May 12 '18 at 13:15
  • Raising a dead thread to point out that "bad" design is sometimes unavoidable, like when working with C libraries that only use native array references. This answer is a good solution to the OP's problem and got an upvote from me, but it's not the only answer for every case. – Michael Hoffmann Jun 09 '21 at 21:53
  • "cleaner", "safer" and "less clunky" is just your opinion. There are cases when it is much cleaner to pass a string literal as an argument, which just so happens to be a C-array. There's no clean way to create `std::array` from a string literal. And there's also no way to use `string_view::size()` within a constexpr context, unlike `N` in `const T (&)[N]`. In general it seams to me that `std::array` is a solution for an XY problem, which is due to impossibility of returning arrays in the first place and decaying to pointer. – Sergey Kolesnik Jan 12 '23 at 17:00
  • @SergeyKolesnik cleaner, safer and "less clunky" was recommendation for a beginner developer, as dealing with single object which have similar semantics like simple type `int` vs all complications for C style array. If you are experienced developer you can decide what works for you best, but examples you made are definitely not for beginners. – Slava Jan 13 '23 at 18:30
3

Some alternatives I think are superior in C++14 onwards:

auto and array reference trailing return type:

template <typename T, std::size_t N>
auto array_ref_test(T(&array)[N]) -> T(&)[N]
{
    return array;
}

decltype(auto):

template <typename T, std::size_t N>
decltype(auto) array_ref_test(T(&array)[N])
{
    return array;
}

Example usage:

int array_ref_ [] = { 1, 2, 3, 4, 5 };   
decltype(auto) array_ref_result = array_ref_test(array_ref_); //'decltype(auto)' preserves reference unlike 'auto'
std::cout << std::size(array_ref_result);
Ricky65
  • 1,657
  • 18
  • 22
1

To answer this question one can design a solution around std::reference_wrapper. This code compiles and runs.

#pragma once
#include <array>
#include <functional>
#include <cstdlib>
#include <ctime>

namespace {

/// <summary>
/// some generic utility function 
/// with argument declared as 
/// native array reference
/// for testing purposes only
/// </summary>
template<typename T, std::size_t N >
inline void array_util_function( T (&arr_ref) [N] ) {
    auto random = [](int max_val, int min_val = 1) -> int {
        // use current time as seed for 
        // random generator, but only once
        static auto initor = []() { 
            std::srand((unsigned)std::time(nullptr)); return 0; 
        }();

        return min_val + std::rand() / ((RAND_MAX + 1u) / max_val);
    };

    for (auto & element : arr_ref) {
        element = random(N);
    }
}

// the key abstraction
template <typename T, std::size_t N>
using native_arr_ref_wrapper = std::reference_wrapper<T[N]>;

template<typename T, std::size_t N >
constexpr std::size_t array_size(T(&arr_ref)[N]) {
    return N;
}

// return the size of the native array
// contained within
template<typename T, std::size_t N >
constexpr std::size_t array_size(native_arr_ref_wrapper<T,N> & narw_) {
    return array_size(narw_.get());
}

/// <summary>
/// returns std::reference_wrapper copy
/// that contains reference to native array 
/// in turn contained inside the std::array
/// argument
/// </summary>
template<typename T, std::size_t N >
    inline auto
        native_arr_ref(const std::array<T, N> & std_arr)
        -> native_arr_ref_wrapper<T,N>
{
    using nativarref = T(&)[N];

    return std::ref(
            (nativarref)*(std_arr.data())
        );
}

    /// <summary>
    /// returns std::reference_wrapper copy
    /// that contains reference to native array 
    /// </summary>
    template<typename T, std::size_t N >
    inline auto
        native_arr_ref(const T (& native_arr)[N] )
        -> native_arr_ref_wrapper<T, N>
    {
        using nativarref = T(&)[N];

        return std::ref(
            (nativarref)*(native_arr)
        );
    }

    auto printarr = [](auto arr_ref, const char * prompt = "Array :") {
        printf("\n%s:\n{ ", prompt);
        int j = 0;
        // use the array reference 
        // to see the contents 
        for (const auto & element : arr_ref.get()) {
            printf(" %d:%d ", j++, element);
        }
        printf(" }\n");
    };

// arf == native array reference
void test_ () {

using std::array;
// the arf type is 
// std::reference_wrapper<int[10]> 
auto arf  = native_arr_ref(array<int, 10>{});
// the arf type is same here 
// but made from a native array
auto ar2 = native_arr_ref({ 0,1,2,3,4,5,6,7,8,9 });
printarr(ar2);

// notice how argument to native_arr_ref()
// does not exist here any more
// but, wrapper contains the copy 
// of the native array  

// type is here: int[10]&
auto & native_arr_ref = arf.get();

// due to array type decay 
// the type is here: int *
auto native_arr_ptr = arf.get();

// size_t
const auto size = array_size(arf);

// this function requires
// native array as argument
// this is how we use arf in place of 
// native arrays
array_util_function(arf.get());

printarr(arf);

    }
  }
1

Your function's signature would look like this:

int(&A::adapter(/*input_args_or_empty*/))[3]{
   return ia;
}
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
Quinn Carver
  • 587
  • 7
  • 14