6

Assume we have some templated struct and sometimes it's template should be an array. How to initialize array in struct?

This

template<typename T>
struct A {
    T x;
    A(T x) : x(x) {}
};


int a[6];
A<decltype(a)> b(a);

generates error during compilation:

error: array initializer must be an initializer list
A(T x) : x(x) {}
         ^

UPD1. More complete code this thing is used in:

template<typename T>
struct A {
    T x;
    A(const T& x) : x(x) {}
    A(const T&& x) : x(std::move(x)) {}
};

template<typename T>
A<typename std::remove_reference<T>::type> make_A(T&& a) {
    return A<typename std::remove_reference<T>::type>(std::forward<T>(a));
}


auto a = make_A("abacaba");
J. S.
  • 425
  • 4
  • 13

4 Answers4

4

A general solution is to provide a special constructor for arrays (enabled when T is an array) which copies the source array to the struct's array. It works, but discard move semantics for arrays.

#include <iostream>
#include <type_traits>
#include <string>
#include <tuple>

template<typename T>
struct A {
    using value_type = std::remove_const_t<T>;
    value_type x;

    template<class U=T> A(const T&  src, std::enable_if_t<!std::is_array_v<U>, int> = 0) : x(src) {}
    template<class U=T> A(const T&& src, std::enable_if_t<!std::is_array_v<U>, int> = 0) : x(std::move(src)) {}
    template<class U=T> A(const T&  src, std::enable_if_t< std::is_array_v<U>, int> = 0) { std::copy(std::begin(src), std::end(src), std::begin(x)); }
};

template<typename T>
auto make_A(T&& a)
{ return A<typename std::remove_reference_t<T>>(std::forward<T>(a)); }

int main()
{
    auto a1 = make_A("the answer");
    std::ignore = a1;
    auto a2 = make_A(42);
    std::ignore = a2;
}

live demo

If you need T to be const for non-arrays sometimes, an improvement would be to define value_type as T if T is not an array and to std::remove_const_t<T> otherwise.

YSC
  • 38,212
  • 9
  • 96
  • 149
1

I suggest putting all the smarts into make_A, converting C-arrays to std::array<>s so that A<> needs only work with regular types:

namespace detail {
    template<typename T, std::size_t... Is>
    constexpr std::array<T, sizeof...(Is)> to_std_array(T const* const p,
                                                        std::index_sequence<Is...>)
    {
        return {{p[Is]...}};
    }
}

template<typename T>
A<std::decay_t<T>> make_A(T&& x) {
    return {std::forward<T>(x)};
}

template<typename T, std::size_t N>
A<std::array<T, N>> make_A(T const (& x)[N]) {
    return {detail::to_std_array(x, std::make_index_sequence<N>{})};
}

Online Demo

If you're only concerned with hardcoded C-strings in particular (as opposed to C-arrays in general), consider converting to a string_view type rather than std::array<> to potentially save some space.

ildjarn
  • 62,044
  • 9
  • 127
  • 211
0

If it is a special behaviour you want to achieve for C-Strings exclusively, you may just add a special treatment:

// for all non-C-string cases
template<typename T, std::enable_if_t<!std::is_same_v<std::decay_t<T>, const char*>>* = nullptr>
A<typename std::remove_reference<T>::type> make_A(T&& a) {
    return A<typename std::remove_reference<T>::type>(std::forward<T>(a));
}

// in case a C-string got passed
A<std::string> make_A(const std::string& str) {
    return A<std::string>(str);
}

int main()
{
    auto a = make_A("abacaba");
    auto b = make_A(5);
}
Jodocus
  • 7,493
  • 1
  • 29
  • 45
-1

With std::decay it works:

template<typename T>
A<typename std::decay<T>::type> make_A(T&& a) {
    return A<typename std::decay<T>::type>(std::forward<T>(a));
}
J. S.
  • 425
  • 4
  • 13