c++language-lawyertemplate-specializationstdarray

std class specialization - meeting the standard library requirements for the original std::array template


In Is it safe to define a specialization for std::array the questioner asks if it's safe to specialize std::array for a program defined type which has an expensive default constructor. The goal is to instead initialize all elements in the std::array by using a cheap constructor instead of the default constructor.

I'm not questioning that design at this point. I'm curious about the possibilities and, to that problem, I suggested to make use of brace elision on initialization of aggregates, define an inner class and use a special constructor via a tag:

namespace foo {
struct cheap_tag_t {};
inline constexpr cheap_tag_t cheap_tag;

class bar {
public:
    inline bar() { std::cout << "expensive ctor\n"; }
    inline bar(int) { std::cout << "int ctor\n"; }
    inline bar(cheap_tag_t) { std::cout << "cheap ctor\n"; }
};
}  // namespace foo
template <std::size_t N>
    requires (N != 0)     // let the standard array take care of N==0
struct std::array<foo::bar, N> {
    struct inner_array {
        foo::bar data[N];
    };

    inner_array m_data{
        []<std::size_t... I>(std::index_sequence<I...>) -> inner_array {
            return {(void(I), foo::cheap_tag)...};
        }(std::make_index_sequence<N>())
    };

    // ... fully compliant member functions ...
};

I can't find anything in the standard prohibiting this odd specialization. I say it's odd because it has an, to an end user, unexpected feature shown last below:

std::array<foo::bar, 0> b0;    // not using the specialization at all
std::array<foo::bar, 2> b1;    // cheap ctor
std::array<foo::bar, 2> b2{};  // cheap ctor
std::array<foo::bar, 2> b3{1}; // one "int ctor" + one "expensive ctor" (surprised user)

To the language lawyering question:


1. "the standard" I've (tried to) read is the C++23 draft. namespace.std/2

Unless explicitly prohibited, a program may add a template specialization for any standard library class template to namespace std provided that
(a) the added declaration depends on at least one program-defined type and
(b) the specialization meets the standard library requirements for the original template.

If there's a different answer to my question in an earlier, or future, standard that you know of, feel free to mention it. It's not required to get an accepted answer.

2. If the above part of the specialization is ok according to "the standard", but you see other problems ahead making it hard/impossible to fulfill the requirements, please feel free to mention those as well. Again, not required.


Solution

  • Your specialization cannot be used in the following way:

    struct anyconv {
        template<typename T>
        operator T() { return T(); }
    };
    
    std::array<foo::bar, 2> x = {anyconv{}, anyconv{}};
    

    However, anyconv is implicitly convertible to foo::bar and so by the requirement in [array.overview]/2 it should be possible to list-initialize std::array<foo::bar, 2> with 2 initializer-clauses that are of type anyconv.