4

Is there anything in the world of C++ that would make what I'm trying to do possible?

template < typename T
         , size_t Size >
struct array
{
    constexpr T buf[Size];

    constexpr size_t size() const { return Size; }
};

template < typename T
         , size_t Size >
constexpr array<T,Size+1> push_back(array<T,Size> const& arr, T const& val)
{
    array<T,Size+1> arr_out = {{arr.buf, val}};

    return arr_out;
}

What I'm trying to do is create a new array initialized with the data in the other, and put a new element on the end.

Minus the constexpr I can get it to work by loop initializing in the push_back function. It appears you can't do that in constexpr functions, which makes some sense though I think a smart enough compiler could figure that out.

I'm pretty sure it can't be done, but I'd love to be shown wrong.

Xeo
  • 129,499
  • 52
  • 291
  • 397
Edward Strange
  • 40,307
  • 7
  • 73
  • 125
  • modulo my possible misunderstanding of what you want, you can do it with varidic templates and initializer list. there is a duplicate somewhere on SO. it would not surprise if answer to that duplicate was provided by johannes schaub-lib. – Cheers and hth. - Alf Feb 24 '13 at 00:54

3 Answers3

4

Indices trick, yay~

template < typename T
         , size_t Size >
struct array
{
    T buf[Size]; // non-static data members can't be constexpr

    constexpr size_t size() const { return Size; }
};

namespace detail{
template< typename T, size_t N, size_t... Is>
constexpr array<T, N+1> push_back(array<T, N> const& arr, T const& val, indices<Is...>)
{
    // can only do single return statement in constexpr
    return {{arr.buf[Is]..., val}};
}
} // detail::

template < typename T, size_t Size >
constexpr array<T,Size+1> push_back(array<T,Size> const& arr, T const& val)
{
    return detail::push_back(arr, val, build_indices<Size>{});
}

Live example.

Xeo
  • 129,499
  • 52
  • 291
  • 397
  • Have to leave for a dinner, but will check this out. If it works it's miles better than what I came up with. Thanks. – Edward Strange Feb 24 '13 at 01:34
  • Beat me to it :P. Interestingly, this can't be implemented for `std::array`, because `std::array` does not expose `constexpr` accessors. – Mankarse Feb 24 '13 at 01:52
  • 1
    @Mankarse: I think that will be taken care of for C++Next, tagging many many things in the stdlib with `constexpr`. – Xeo Feb 24 '13 at 03:37
  • After looking over the wiki I'm disappointed that I'm not mentioned in the Ministry of Shame. – Edward Strange Feb 26 '13 at 20:18
1

Expanding on Xeo's answer, here is a version which forwards its arguments:

#include <boost/mpl/if.hpp>
#include <cstddef>
#include <utility>
#include <iostream>

template<typename T, std::size_t Size>
struct array
{
    typedef T value_type;
    T buf[Size];

    constexpr std::size_t size() const { return Size; }
};

template<typename T>
struct array_size;

template<typename T, std::size_t Size>
struct array_size<array<T, Size>> {
    static constexpr std::size_t value = Size;
};

template <typename T>
using Bare =
    typename std::remove_cv<typename std::remove_reference<T>::type>::type;

template <typename T>
constexpr T&& forward(typename std::remove_reference<T>::type& t) noexcept {
    return static_cast<T&&>(t);
}

template<typename Array>
using CVValueType = typename boost::mpl::if_<
    std::is_const<Array>,
    typename boost::mpl::if_<
        std::is_volatile<Array>,
        typename Array::value_type const volatile,
        typename Array::value_type const>::type,
    typename boost::mpl::if_<
        std::is_volatile<Array>,
        typename Array::value_type volatile,
        typename Array::value_type>::type
>::type;

template<typename Array>
using ForwardType =
    typename boost::mpl::if_c<
        std::is_lvalue_reference<Array>::value,
        CVValueType<typename std::remove_reference<Array>::type>&,
        CVValueType<typename std::remove_reference<Array>::type>&&>::type;

template <typename Array>
constexpr ForwardType<Array> forward_element(
    CVValueType<typename std::remove_reference<Array>::type>& t) noexcept
{
    return static_cast<ForwardType<Array>>(t);
}

template <std::size_t... Is>
struct indices {};

template <std::size_t N, std::size_t... Is>
struct build_indices
  : build_indices<N-1, N-1, Is...> {};

template <std::size_t... Is>
struct build_indices<0, Is...> : indices<Is...> {};

template<typename Array>
using Enlarged =
    array<typename Bare<Array>::value_type, array_size<Bare<Array>>::value+1>;

template<typename Array, typename T, std::size_t... Is>
constexpr Enlarged<Array> push_back(Array&& arr, T&& val, indices<Is...>)
{
    return {{forward_element<Array>(arr.buf[Is])..., forward<T>(val)}};
}

template <typename Array, typename T>
constexpr Enlarged<Array> push_back(Array&& arr, T&& val)
{
    return push_back(
        forward<Array>(arr),
        forward<T>(val),
        build_indices<array_size<Bare<Array>>::value>{});
}
Mankarse
  • 39,818
  • 11
  • 97
  • 141
  • Does the combination of `constexpr`, rval referenecs, forwarding, and move semantics make sense? What happens when you move a constant? I'd imagine that it kills the `constexpr` aspect? – Edward Strange Feb 28 '13 at 20:36
  • @CrazyEddie Nope, any constructor can be `constexpr`, even a move constructor. Obviously you can't do anything side-effecting inside the constructor (such as changing the contents of the rvalue-reference argument), but you can do all the same stuff you'd do in a `constexpr` copy constructor, member function, or whatever. – Quuxplusone Oct 24 '13 at 09:07
0
namespace detail_
{

template < typename T
         , size_t End >
struct push_backer
{
    template < typename Array
             , typename ... Args>
    static constexpr auto push_back(Array const& arr, Args const& ... args) -> decltype(push_backer<T,End-1>::push_back(arr, arr.buf[End-1],args...))
    {
        return push_backer<T,End-1>::push_back(arr, arr.buf[End-1], args...);
    }
};

template < typename T >
struct push_backer<T,0>
{
    template < size_t Size
             , typename ... Args>
    static constexpr array<T,Size+1> push_back(array<T,Size> const& arr, Args const& ... args)
    {
        return array<T,Size+1>{{args...}};
    }
};

}

template < typename T
         , size_t Size >
constexpr array<T,Size+1> push_back(array<T,Size> const& arr, T const& val)
{
    return detail_::push_backer<T,Size>::push_back(arr, val);
}
Edward Strange
  • 40,307
  • 7
  • 73
  • 125